sc2ai 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/data.json +1 -1
- data/data/data_readable.json +31 -28
- data/lib/docker_build/Dockerfile.ruby +1 -1
- data/lib/sc2ai/api/data.rb +159 -2
- data/lib/sc2ai/api/tech_tree.rb +22 -0
- data/lib/sc2ai/api/tech_tree_data.rb +19 -8
- data/lib/sc2ai/connection/requests.rb +26 -11
- data/lib/sc2ai/connection.rb +1 -1
- data/lib/sc2ai/local_play/client_manager.rb +9 -1
- data/lib/sc2ai/player/actions.rb +17 -0
- data/lib/sc2ai/player/debug.rb +1 -0
- data/lib/sc2ai/player/game_state.rb +8 -5
- data/lib/sc2ai/player/geometry.rb +100 -16
- data/lib/sc2ai/player/previous_state.rb +3 -3
- data/lib/sc2ai/player/units.rb +116 -6
- data/lib/sc2ai/player.rb +3 -1
- data/lib/sc2ai/protocol/_meta_documentation.rb +2 -0
- data/lib/sc2ai/protocol/extensions/ability_remapable.rb +22 -0
- data/lib/sc2ai/protocol/extensions/point.rb +3 -1
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +6 -0
- data/lib/sc2ai/protocol/extensions/position.rb +41 -4
- data/lib/sc2ai/protocol/extensions/unit.rb +35 -3
- data/lib/sc2ai/protocol/extensions/unit_type_data.rb +20 -0
- data/lib/sc2ai/unit_group/action_ext.rb +9 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +18 -1
- data/lib/sc2ai/unit_group/geo_ext.rb +28 -0
- data/lib/sc2ai/unit_group.rb +12 -1
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +498 -87
- metadata +26 -23
data/lib/sc2ai/player/debug.rb
CHANGED
@@ -16,9 +16,12 @@ module Sc2
|
|
16
16
|
|
17
17
|
extend Forwardable
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
attr_writer :game_loop
|
20
|
+
|
21
|
+
# @return [Integer] current game loop
|
22
|
+
def game_loop
|
23
|
+
@game_loop || 0
|
24
|
+
end
|
22
25
|
|
23
26
|
# @!attribute game_info [rw]
|
24
27
|
# Access useful game information. Used in parsed pathing grid, terrain height, placement grid.
|
@@ -38,7 +41,7 @@ module Sc2
|
|
38
41
|
attr_accessor :game_info_loop
|
39
42
|
|
40
43
|
# Determines if your game_info will be refreshed at this moment
|
41
|
-
# Has a hard-capped refresh of only ever
|
44
|
+
# Has a hard-capped refresh of only ever 4 steps
|
42
45
|
# In general game_info is only refreshed Player::Bot reads from pathing_grid or placement_grid
|
43
46
|
# @return [Boolean]
|
44
47
|
def game_info_stale?
|
@@ -47,7 +50,7 @@ module Sc2
|
|
47
50
|
|
48
51
|
# Note: No minimum step count set anymore
|
49
52
|
# We can do something like, only updating every 2+ frames:
|
50
|
-
game_info_loop +
|
53
|
+
game_info_loop + 4 <= game_loop
|
51
54
|
end
|
52
55
|
|
53
56
|
# @!attribute data
|
@@ -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,7 @@ 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
32
|
end
|
33
33
|
|
34
34
|
# Returns zero to map_width as range
|
@@ -83,6 +83,15 @@ module Sc2
|
|
83
83
|
@parsed_placement_grid
|
84
84
|
end
|
85
85
|
|
86
|
+
# Whether this tile is where an expansion is supposed to be placed.
|
87
|
+
# To see if a unit/structure is blocking an expansion, pass their coordinates to this method.
|
88
|
+
# @param x [Float, Integer]
|
89
|
+
# @param y [Float, Integer]
|
90
|
+
# @return [Boolean] true if location has creep on it
|
91
|
+
def expo_placement?(x:, y:)
|
92
|
+
expo_placement_grid[y.to_i, x.to_i] == 1
|
93
|
+
end
|
94
|
+
|
86
95
|
# Returns a grid where ony the expo locations are marked
|
87
96
|
# @return [Numo::Bit]
|
88
97
|
def expo_placement_grid
|
@@ -259,6 +268,13 @@ module Sc2
|
|
259
268
|
parsed_terrain_height[y.to_i, x.to_i]
|
260
269
|
end
|
261
270
|
|
271
|
+
# Returns the terrain height (z) at position x and y for a point
|
272
|
+
# @param position [Sc2::Position]
|
273
|
+
# @return [Float] z axis position between -16 and 16
|
274
|
+
def terrain_height_for_pos(position)
|
275
|
+
terrain_height(x: position.x, y: position.y)
|
276
|
+
end
|
277
|
+
|
262
278
|
# Returns a parsed terrain_height from bot.game_info.start_raw.
|
263
279
|
# Each value in [row][column] holds a float value which is the z height
|
264
280
|
# @return [Numo::SFloat] Numo array
|
@@ -334,15 +350,17 @@ module Sc2
|
|
334
350
|
end
|
335
351
|
|
336
352
|
# Provides parsed minimap representation of creep spread
|
353
|
+
# Caches for 4 frames
|
337
354
|
# @return [Numo::Bit] Numo array
|
338
355
|
def parsed_creep
|
339
|
-
if @parsed_creep.nil?
|
356
|
+
if @parsed_creep.nil? || @parsed_creep[1] + 4 < bot.game_loop
|
340
357
|
image_data = bot.observation.raw_data.map_state.creep
|
341
358
|
# Fix endian for Numo bit parser
|
342
359
|
data = image_data.data.unpack("b*").pack("B*")
|
343
|
-
|
360
|
+
result = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
|
361
|
+
@parsed_creep = [result, bot.game_loop]
|
344
362
|
end
|
345
|
-
@parsed_creep
|
363
|
+
@parsed_creep[0]
|
346
364
|
end
|
347
365
|
|
348
366
|
# TODO: Removing. Better name or more features for this? Maybe check nearest units.
|
@@ -537,16 +555,75 @@ module Sc2
|
|
537
555
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
538
556
|
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
539
557
|
def minerals_for_base(base)
|
540
|
-
|
541
|
-
|
542
|
-
bot.neutral.minerals.
|
558
|
+
base_resources = resources_for_base(base)
|
559
|
+
cached_tags = base_resources.minerals.tags
|
560
|
+
observed_tags = bot.neutral.minerals.tags
|
561
|
+
|
562
|
+
# BACK-STORY: Mineral id's are fixed when in vision.
|
563
|
+
# Snapshots get random id's every time an object leaves vision.
|
564
|
+
# At game launch when we calculate and save minerals, which are mostly snapshot.
|
565
|
+
|
566
|
+
# Currently, we might have moved vision over minerals, so that their id's have changed.
|
567
|
+
# The alive object share a Position with our cached one, so we can get the correct id and update our cache.
|
568
|
+
|
569
|
+
# PERF: Fix takes 0.70ms, cache takes 0.10ms - we mostly call cached. This is the way.
|
570
|
+
# PERF: In contrast, repeated calls to neutral.minerals.units_in_circle? always costs 0.22ms
|
571
|
+
|
572
|
+
missing_tags = cached_tags - observed_tags
|
573
|
+
unless missing_tags.empty?
|
574
|
+
other_alive_minerals = bot.neutral.minerals.slice(*(observed_tags - cached_tags))
|
575
|
+
# For each missing calculated mineral patch...
|
576
|
+
missing_tags.each do |tag|
|
577
|
+
missing_resource = base_resources.delete(tag)
|
578
|
+
|
579
|
+
# Find an alive mineral at that position
|
580
|
+
new_resource = other_alive_minerals.find { |live_mineral| live_mineral.pos == missing_resource.pos }
|
581
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
base_resources.minerals
|
543
586
|
end
|
544
587
|
|
545
588
|
# Gets geysers for a base or base position
|
546
589
|
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
547
590
|
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
548
591
|
def geysers_for_base(base)
|
549
|
-
|
592
|
+
# @see #minerals_for_base for backstory on these fixes
|
593
|
+
base_resources = resources_for_base(base)
|
594
|
+
cached_tags = base_resources.geysers.tags
|
595
|
+
observed_tags = bot.neutral.geysers.tags
|
596
|
+
|
597
|
+
missing_tags = cached_tags - observed_tags
|
598
|
+
unless missing_tags.empty?
|
599
|
+
other_alive_geysers = bot.neutral.geysers.slice(*(observed_tags - cached_tags))
|
600
|
+
# For each missing calculated geyser patch...
|
601
|
+
missing_tags.each do |tag|
|
602
|
+
missing_resource = base_resources.delete(tag)
|
603
|
+
|
604
|
+
# Find an alive geyser at that position
|
605
|
+
new_resource = other_alive_geysers.find { |live_geyser| live_geyser.pos == missing_resource.pos }
|
606
|
+
base_resources.add(new_resource) unless new_resource.nil?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
base_resources.geysers
|
611
|
+
end
|
612
|
+
|
613
|
+
# Gets geysers which have not been taken for a base or base position
|
614
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
615
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
616
|
+
def geysers_open_for_base(base)
|
617
|
+
geysers = geysers_for_base(base)
|
618
|
+
|
619
|
+
# Mineral-only base, return nothing
|
620
|
+
return UnitGroup.new if geysers.size == 0
|
621
|
+
|
622
|
+
# Reject all which have a gas structure on-top
|
623
|
+
gas_positions = bot.structures.gas.map { |gas| gas.pos }
|
624
|
+
geysers.reject do |geyser|
|
625
|
+
gas_positions.include?(geyser.pos)
|
626
|
+
end
|
550
627
|
end
|
551
628
|
|
552
629
|
# @private
|
@@ -554,6 +631,7 @@ module Sc2
|
|
554
631
|
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
555
632
|
private def resources_for_base(base)
|
556
633
|
pos = base.is_a?(Api::Unit) ? base.pos : base
|
634
|
+
pos = pos.to_p2d if base.is_a?(Api::Point)
|
557
635
|
|
558
636
|
# If we have a base setup for this exact position, use it
|
559
637
|
if expansions.has_key?(pos)
|
@@ -593,12 +671,17 @@ module Sc2
|
|
593
671
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
594
672
|
length = 1 if length < 1
|
595
673
|
@_build_coordinates ||= {}
|
596
|
-
cache_key = [length, on_creep].hash
|
674
|
+
cache_key = [length, on_creep, in_power].hash
|
597
675
|
return @_build_coordinates[cache_key] if !@_build_coordinates[cache_key].nil? && !bot.game_info_stale?
|
598
676
|
|
599
677
|
result = []
|
600
678
|
input_grid = parsed_pathing_grid & parsed_placement_grid & ~expo_placement_grid
|
601
|
-
input_grid =
|
679
|
+
input_grid = if on_creep
|
680
|
+
parsed_creep & input_grid
|
681
|
+
else
|
682
|
+
~parsed_creep & input_grid
|
683
|
+
end
|
684
|
+
|
602
685
|
input_grid = parsed_power_grid & input_grid if in_power
|
603
686
|
|
604
687
|
# Dimensions
|
@@ -651,16 +734,17 @@ module Sc2
|
|
651
734
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
652
735
|
# @param target [Api::Unit, Sc2::Position] near where to find a placement
|
653
736
|
# @param random [Integer] number of nearest points to randomly choose from. 1 for nearest point.
|
737
|
+
# @param in_power [Boolean] whether this must be on a power field
|
654
738
|
# @return [Api::Point2D, nil] buildable location, nil if no buildable location found
|
655
|
-
def build_placement_near(length:, target:, random: 1)
|
739
|
+
def build_placement_near(length:, target:, random: 1, in_power: false)
|
656
740
|
target = target.pos if target.is_a? Api::Unit
|
657
741
|
random = 1 if random.to_i.negative?
|
658
742
|
length = 1 if length < 1
|
659
743
|
on_creep = bot.race == Api::Race::Zerg
|
660
744
|
|
661
|
-
coordinates = build_coordinates(length:, on_creep:)
|
745
|
+
coordinates = build_coordinates(length:, on_creep:, in_power:)
|
746
|
+
cache_key = coordinates.hash
|
662
747
|
@_build_coordinate_tree ||= {}
|
663
|
-
cache_key = [length, on_creep].hash
|
664
748
|
if @_build_coordinate_tree[cache_key].nil?
|
665
749
|
@_build_coordinate_tree[cache_key] = Kdtree.new(
|
666
750
|
coordinates.each_with_index.map { |coords, index| coords + [index] }
|
@@ -813,14 +897,14 @@ module Sc2
|
|
813
897
|
# @example
|
814
898
|
# Randomly randomly adjust both x and y by a range of -3.5 or +3.5
|
815
899
|
# geo.point_random_near(point: structures.hq.first, offset: 3.5)
|
816
|
-
# @param pos [Sc2::
|
900
|
+
# @param pos [Sc2::Position]
|
817
901
|
# @param offset [Float]
|
818
902
|
# @return [Api::Point2D]
|
819
903
|
def point_random_near(pos:, offset: 1.0)
|
820
904
|
pos.random_offset(offset)
|
821
905
|
end
|
822
906
|
|
823
|
-
# @param pos [Sc2::
|
907
|
+
# @param pos [Sc2::Position]
|
824
908
|
# @param radius [Float]
|
825
909
|
# @return [Api::Point2D]
|
826
910
|
def point_random_on_circle(pos:, radius: 1.0)
|
@@ -24,7 +24,7 @@ module Sc2
|
|
24
24
|
@status = bot.status
|
25
25
|
@game_info = bot.game_info
|
26
26
|
@observation = bot.observation
|
27
|
-
|
27
|
+
@game_loop = bot.observation.game_loop
|
28
28
|
@spent_minerals = bot.spent_minerals
|
29
29
|
@spent_vespene = bot.spent_vespene
|
30
30
|
@spent_supply = bot.spent_supply
|
@@ -36,13 +36,13 @@ module Sc2
|
|
36
36
|
# Override to modify the previous frame before being set to current
|
37
37
|
# @param bot [Sc2::Player::Bot]
|
38
38
|
def before_reset(bot)
|
39
|
-
#
|
39
|
+
# no op
|
40
40
|
end
|
41
41
|
|
42
42
|
# Override to modify previous frame after reset is complete
|
43
43
|
# @param bot [Sc2::Player::Bot]
|
44
44
|
def after_reset(bot)
|
45
|
-
#
|
45
|
+
# no op
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -30,6 +30,94 @@ module Sc2
|
|
30
30
|
# @return [Sc2::UnitGroup] a group of neutral units
|
31
31
|
attr_accessor :effects # not a unit
|
32
32
|
|
33
|
+
# Returns the upgrade ids you have acquired such as weapon upgrade and armor upgrade ids.
|
34
|
+
# Shorthand for observation.raw_data.player.upgrade_ids
|
35
|
+
# @!attribute [r] upgrades_completed
|
36
|
+
# @return [Array<Integer>] a group of neutral units
|
37
|
+
def upgrades_completed = observation&.raw_data&.player&.upgrade_ids.to_a || [] # not a unit
|
38
|
+
|
39
|
+
# Returns the upgrade ids which are researching or queued
|
40
|
+
# Not set for enemy.
|
41
|
+
# @return [Array<Integer>]
|
42
|
+
def upgrades_in_progress
|
43
|
+
# We need to scan every structure which performs upgrades for any order with an upgrade ability
|
44
|
+
|
45
|
+
result = []
|
46
|
+
# Loop every upgrade structure
|
47
|
+
structures
|
48
|
+
.select_type(Api::TechTree.upgrade_structure_unit_type_ids)
|
49
|
+
.each do |structure|
|
50
|
+
next unless structure.is_active? # Skip idle
|
51
|
+
|
52
|
+
# Check if any order at a structure contains an upgrade ability
|
53
|
+
structure.orders.each do |order|
|
54
|
+
Api::TechTree.upgrade_ability_data(structure.unit_type).each do |upgrade_id, update_info|
|
55
|
+
if update_info[:ability] == order.ability_id
|
56
|
+
# Save the upgrade_id
|
57
|
+
result << upgrade_id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# If the API told use it's complete, but an order still lingers, trust the API
|
64
|
+
result - upgrades_completed
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the upgrade ids which are researching or queued
|
68
|
+
# @return [Boolean]
|
69
|
+
def upgrade_in_progress?(upgrade_id)
|
70
|
+
structure_unit_type_id = Api::TechTree.upgrade_researched_from(upgrade_id: upgrade_id)
|
71
|
+
research_ability_id = Api::TechTree.upgrade_research_ability_id(upgrade_id: upgrade_id)
|
72
|
+
structures.select_type(structure_unit_type_id).each do |structure|
|
73
|
+
structure.orders.each do |order|
|
74
|
+
return true if order.ability_id == research_ability_id
|
75
|
+
end
|
76
|
+
end
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# For this unit type, tells you how many are in progress by checking orders for all it's sources.
|
81
|
+
# @return [Integer]
|
82
|
+
def units_in_progress(unit_type_id)
|
83
|
+
source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
|
84
|
+
|
85
|
+
# When building from LARVA, check the intermediate models
|
86
|
+
if source_unit_types.include?(Api::UnitTypeId::LARVA)
|
87
|
+
source_unit_types << Api::UnitTypeId::EGG
|
88
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BANELING)
|
89
|
+
# For certain Zerg types, return the count of specific intermediate egg/cocoon
|
90
|
+
return units.select_type(Api::UnitTypeId::BANELINGCOCOON).size
|
91
|
+
elsif source_unit_types.include?(Api::UnitTypeId::RAVAGER)
|
92
|
+
return units.select_type(Api::UnitTypeId::RAVAGERCOCOON).size
|
93
|
+
elsif source_unit_types.include?(Api::UnitTypeId::OVERSEER)
|
94
|
+
return units.select_type(Api::UnitTypeId::OVERLORDCOCOON).size
|
95
|
+
elsif source_unit_types.include?(Api::UnitTypeId::LURKERMP)
|
96
|
+
return units.select_type(Api::UnitTypeId::LURKERMPEGG).size
|
97
|
+
elsif source_unit_types.include?(Api::UnitTypeId::BROODLORD)
|
98
|
+
return units.select_type(Api::UnitTypeId::BROODLORDCOCOON).size
|
99
|
+
end
|
100
|
+
|
101
|
+
unit_create_ability = Api::TechTree.unit_type_creation_ability_id(
|
102
|
+
source: source_unit_types.first,
|
103
|
+
target: unit_type_id
|
104
|
+
)
|
105
|
+
|
106
|
+
origin = if unit_data(source_unit_types.first).attributes.include?(:Structure)
|
107
|
+
structures
|
108
|
+
else
|
109
|
+
units
|
110
|
+
end
|
111
|
+
total_in_progress = origin.select_type(source_unit_types).sum do |source|
|
112
|
+
source.orders.count do |order|
|
113
|
+
true if order.ability_id == unit_create_ability
|
114
|
+
end
|
115
|
+
end
|
116
|
+
total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING
|
117
|
+
|
118
|
+
total_in_progress
|
119
|
+
end
|
120
|
+
|
33
121
|
# An array of Protoss power sources, which have a point, radius and unit tag
|
34
122
|
# @!attribute power_sources
|
35
123
|
# @return [Array<Api::PowerSource>] an array of power sources
|
@@ -107,6 +195,13 @@ module Sc2
|
|
107
195
|
data.abilities[ability_id]
|
108
196
|
end
|
109
197
|
|
198
|
+
# Returns static [Api::UpgradeData] for an upgrade id
|
199
|
+
# @param upgrade_id [Integer] Api::UpgradeId::*
|
200
|
+
# @return [Api::UpgradeData]
|
201
|
+
def upgrade_data(upgrade_id)
|
202
|
+
data.upgrades[upgrade_id]
|
203
|
+
end
|
204
|
+
|
110
205
|
# Checks unit data for an attribute value
|
111
206
|
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
112
207
|
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::Mechanical or :Mechanical
|
@@ -141,17 +236,13 @@ module Sc2
|
|
141
236
|
def subtract_cost(unit_type_id)
|
142
237
|
unit_type_data = unit_data(unit_type_id)
|
143
238
|
|
144
|
-
# food_required is a float. ensure half units are counted as full
|
145
|
-
# TODO: Extend UnitTypeData message. def food_required = unit_id == Api::UnitTypeId::ZERGLING ? 1 : send("method_missing", :food_required)
|
146
|
-
supply_cost = unit_type_data.food_required
|
147
|
-
supply_cost = 1 if unit_type_id == Api::UnitTypeId::ZERGLING
|
148
|
-
|
149
239
|
@spent_minerals += unit_type_data.mineral_cost
|
150
240
|
@spent_vespene += unit_type_data.vespene_cost
|
151
|
-
@spent_supply +=
|
241
|
+
@spent_supply += unit_type_data.food_required
|
152
242
|
end
|
153
243
|
|
154
244
|
# Checks whether you have the resources to construct quantity of unit type
|
245
|
+
# @return [Boolean]
|
155
246
|
def can_afford?(unit_type_id:, quantity: 1)
|
156
247
|
unit_type_data = unit_data(unit_type_id)
|
157
248
|
return false if unit_type_data.nil?
|
@@ -178,6 +269,25 @@ module Sc2
|
|
178
269
|
true
|
179
270
|
end
|
180
271
|
|
272
|
+
# Checks whether you have the resources to
|
273
|
+
# @return [Boolean]
|
274
|
+
def can_afford_upgrade?(upgrade_id)
|
275
|
+
unit_type_data = upgrade_data(upgrade_id)
|
276
|
+
return false if unit_type_data.nil?
|
277
|
+
|
278
|
+
mineral_cost = unit_type_data.mineral_cost
|
279
|
+
if common.minerals - spent_minerals < mineral_cost
|
280
|
+
return false # not enough minerals
|
281
|
+
end
|
282
|
+
|
283
|
+
vespene_cost = unit_type_data.vespene_cost
|
284
|
+
if common.vespene - spent_vespene < vespene_cost
|
285
|
+
return false # you require more vespene gas
|
286
|
+
end
|
287
|
+
|
288
|
+
true
|
289
|
+
end
|
290
|
+
|
181
291
|
private
|
182
292
|
|
183
293
|
# @private
|
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
|
-
|
236
|
+
print "\e[2K#{@step_count} 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
|
@@ -21,6 +21,8 @@
|
|
21
21
|
# class Point < Google::Protobuf::AbstractMessage; end;
|
22
22
|
# # Protobuf virtual class.
|
23
23
|
# class Unit < Google::Protobuf::AbstractMessage; end;
|
24
|
+
# # Protobuf virtual class.
|
25
|
+
# class UnitTypeData < Google::Protobuf::AbstractMessage; end;
|
24
26
|
# end
|
25
27
|
|
26
28
|
# Protobuf enums ---
|
@@ -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
|
@@ -10,6 +10,12 @@ module Api
|
|
10
10
|
self.class == other.class && hash == other.hash
|
11
11
|
end
|
12
12
|
|
13
|
+
# Create a new 3d Point, by adding a y axis.
|
14
|
+
# @return [Api::Point]
|
15
|
+
def to_3d(z:)
|
16
|
+
Api::Point[x, y, z]
|
17
|
+
end
|
18
|
+
|
13
19
|
# Adds additional functionality to message class Api::Point2D
|
14
20
|
module ClassMethods
|
15
21
|
# Shorthand for creating an instance for [x, y]
|
@@ -1,12 +1,23 @@
|
|
1
1
|
module Sc2
|
2
2
|
# A unified construct that tames Api::* messages which contain location data
|
3
|
-
# Items which are of type Sc2::
|
3
|
+
# Items which are of type Sc2::Position will have #x and #y property at the least.
|
4
4
|
module Position
|
5
5
|
# Tolerance for floating-point comparisons.
|
6
6
|
TOLERANCE = 1e-9
|
7
7
|
|
8
8
|
# Basic operations
|
9
9
|
|
10
|
+
# Loose equality matches on floats x and y.
|
11
|
+
# We never check z-axis, because the map is single-level.
|
12
|
+
# TODO: We should almost certainly introduce TOLERANCE here, but verify it's cost first.
|
13
|
+
def ==(other)
|
14
|
+
if other.is_a? Position
|
15
|
+
x == other.x && y == other.y
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
10
21
|
# A new point representing the sum of this point and the other point.
|
11
22
|
# @param other [Api::Point2D, Numeric] The other point/number to add.
|
12
23
|
# @return [Api::Point2D]
|
@@ -50,12 +61,38 @@ module Sc2
|
|
50
61
|
# @see #divide
|
51
62
|
alias_method :/, :divide
|
52
63
|
|
53
|
-
#
|
54
|
-
#
|
64
|
+
# Returns x coordinate
|
65
|
+
# @return [Float]
|
66
|
+
def x
|
67
|
+
# Perf: Memoizing attributes which are hit hard, show gain
|
68
|
+
@x ||= send(:method_missing, :x)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Sets x coordinate
|
72
|
+
# @return [Float]
|
73
|
+
def x=(x)
|
74
|
+
send(:method_missing, :x=, x)
|
75
|
+
@x = x
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns y coordinate
|
79
|
+
# @return [Float]
|
55
80
|
def y
|
81
|
+
# Bug: Psych implements method 'y' on Kernel, but protobuf uses method_missing to read AbstractMethod
|
82
|
+
# We send method missing ourselves when y to fix this chain.
|
56
83
|
# This is correct, but an unnecessary conditional:
|
57
84
|
# raise NoMethodError unless location == self
|
58
|
-
|
85
|
+
|
86
|
+
# Perf: Memoizing attributes which are hit hard, show gain
|
87
|
+
|
88
|
+
@y ||= send(:method_missing, :y)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sets y coordinate
|
92
|
+
# @return [Float]
|
93
|
+
def y=(y)
|
94
|
+
send(:method_missing, :y=, y)
|
95
|
+
@y = y
|
59
96
|
end
|
60
97
|
|
61
98
|
# Randomly adjusts both x and y by a range of: -offset..offset
|
@@ -9,6 +9,22 @@ module Api
|
|
9
9
|
tag || super
|
10
10
|
end
|
11
11
|
|
12
|
+
# Returns an integer unique identifier
|
13
|
+
# If the unit goes out of vision and is snapshot-able, they get a random id
|
14
|
+
# - Such a unit gets the same unit tag when it re-enters vision
|
15
|
+
# @return [Integer]
|
16
|
+
def tag
|
17
|
+
# Perf: This speeds up hash and therefore common UnitGroup operations. Sometimes 3x!
|
18
|
+
@tag ||= send(:method_missing, :tag)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets unit tag
|
22
|
+
# @return [Integer]
|
23
|
+
def tag=(tag)
|
24
|
+
send(:method_missing, :tag=, tag)
|
25
|
+
@tag = tag
|
26
|
+
end
|
27
|
+
|
12
28
|
# Every unit gets access back to the bot to allow api access.
|
13
29
|
# For your own units, this allows API access.
|
14
30
|
# @return [Sc2::Player] player with active connection
|
@@ -37,9 +53,8 @@ module Api
|
|
37
53
|
# Checks unit data for an attribute value
|
38
54
|
# @return [Boolean] whether unit has attribute
|
39
55
|
# @example
|
40
|
-
# has_attribute?(Api::
|
41
|
-
# has_attribute?(
|
42
|
-
# has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
|
56
|
+
# unit.has_attribute?(Api::Attribute::Mechanical)
|
57
|
+
# unit.has_attribute?(:Mechanical)
|
43
58
|
def has_attribute?(attribute)
|
44
59
|
attributes.include? attribute
|
45
60
|
end
|
@@ -162,6 +177,15 @@ module Api
|
|
162
177
|
|
163
178
|
# @!endgroup Virtual properties
|
164
179
|
|
180
|
+
# Whether unit is effected by buff_id
|
181
|
+
# @example
|
182
|
+
# unit.has_buff??(Api::BuffId::QUEENSPAWNLARVATIMER)
|
183
|
+
# @param [Integer] buff_id
|
184
|
+
# @return [Boolean]
|
185
|
+
def has_buff?(buff_id)
|
186
|
+
buff_ids.include?(buff_id)
|
187
|
+
end
|
188
|
+
|
165
189
|
# @!group Actions
|
166
190
|
|
167
191
|
# Performs action on this unit
|
@@ -233,10 +257,18 @@ module Api
|
|
233
257
|
|
234
258
|
# Issues repair command on target
|
235
259
|
# @param target [Api::Unit, Integer] is a unit or unit tag
|
260
|
+
# @param queue_command [Boolean] shift+command
|
236
261
|
def repair(target:, queue_command: false)
|
237
262
|
action(ability_id: Api::AbilityId::EFFECT_REPAIR, target:, queue_command:)
|
238
263
|
end
|
239
264
|
|
265
|
+
# Research a specific upgrade
|
266
|
+
# @param upgrade_id [Integer] Api::UnitTypeId the unit type which will do the creation
|
267
|
+
# @param queue_command [Boolean] shift+command
|
268
|
+
def research(upgrade_id:, queue_command: false)
|
269
|
+
@bot.research(units: self, upgrade_id:, queue_command:)
|
270
|
+
end
|
271
|
+
|
240
272
|
# @!endgroup Actions
|
241
273
|
#
|
242
274
|
# Debug ----
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Api
|
4
|
+
# Adds additional functionality to message object Api::UnitTypeData
|
5
|
+
module UnitTypeDataExtension
|
6
|
+
# @!attribute mineral_cost_sum
|
7
|
+
# Sum of all morphs mineral cost
|
8
|
+
# i.e. 550M Orbital command = 400M CC + 150M Upgrade
|
9
|
+
# i.e. 350M Hatchery = 50M Drone + 300M Build
|
10
|
+
# @return [Integer] sum of mineral costs
|
11
|
+
attr_accessor :mineral_cost_sum
|
12
|
+
|
13
|
+
# @!attribute vespene_cost_sum
|
14
|
+
# Sum of all morphs vespene gas cost
|
15
|
+
# i.e. 250G Broodlord = 100G Corruptor + 150G Morph
|
16
|
+
# @return [Integer] sum of vespene gas costs
|
17
|
+
attr_accessor :vespene_cost_sum
|
18
|
+
end
|
19
|
+
end
|
20
|
+
Api::UnitTypeData.include Api::UnitTypeDataExtension
|