sc2ai 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|