sc2ai 0.0.3 → 0.0.5
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/sc2ai/api/data.rb +162 -2
- data/lib/sc2ai/api/tech_tree.rb +22 -0
- data/lib/sc2ai/api/tech_tree_data.rb +19 -8
- data/lib/sc2ai/connection.rb +1 -1
- data/lib/sc2ai/local_play/client_manager.rb +9 -1
- data/lib/sc2ai/player/actions.rb +21 -3
- data/lib/sc2ai/player/debug.rb +51 -4
- data/lib/sc2ai/player/geometry.rb +80 -5
- data/lib/sc2ai/player/previous_state.rb +2 -2
- data/lib/sc2ai/player/units.rb +77 -8
- data/lib/sc2ai/player.rb +7 -7
- data/lib/sc2ai/protocol/_meta_documentation.rb +2 -0
- data/lib/sc2ai/protocol/extensions/point.rb +13 -1
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +6 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +64 -10
- data/lib/sc2ai/protocol/extensions/unit_type_data.rb +20 -0
- data/lib/sc2ai/unit_group/action_ext.rb +31 -2
- data/lib/sc2ai/unit_group/filter_ext.rb +6 -0
- data/lib/sc2ai/unit_group.rb +31 -11
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/my_bot.rb.tt +2 -3
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +10382 -0
- metadata +43 -69
data/lib/sc2ai/player/debug.rb
CHANGED
@@ -14,6 +14,7 @@ module Sc2
|
|
14
14
|
# @param debug_command [Api::DebugCommand]
|
15
15
|
# @return [void]
|
16
16
|
def queue_debug_command(debug_command)
|
17
|
+
@debug_command_queue ||= []
|
17
18
|
@debug_command_queue << debug_command
|
18
19
|
end
|
19
20
|
|
@@ -37,6 +38,52 @@ module Sc2
|
|
37
38
|
)
|
38
39
|
end
|
39
40
|
|
41
|
+
# Prints text on screen from top and left
|
42
|
+
# @param text [String] will respect newlines
|
43
|
+
# @param left_percent [Numeric] range 0..100. percent from left of screen
|
44
|
+
# @param top_percent [Numeric] range 0..100. percent from top of screen
|
45
|
+
# @param color [Api::Color] default white
|
46
|
+
# @param size [Size] of font, default 14px
|
47
|
+
# @return [void]
|
48
|
+
def debug_text_screen(text, left_percent: 1.0, top_percent: 1.0, color: nil, size: 14)
|
49
|
+
queue_debug_command Api::DebugCommand.new(
|
50
|
+
draw: Api::DebugDraw.new(
|
51
|
+
text: [
|
52
|
+
Api::DebugText.new(
|
53
|
+
text:,
|
54
|
+
virtual_pos: Api::Point.new(
|
55
|
+
x: left_percent.to_f / 100,
|
56
|
+
y: top_percent.to_f / 100
|
57
|
+
),
|
58
|
+
color:,
|
59
|
+
size:
|
60
|
+
)
|
61
|
+
]
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Prints text on screen at 3d world position
|
67
|
+
# @param text [String] will respect newlines
|
68
|
+
# @param point [Api::Point] point in the world, i.e. unit.pos
|
69
|
+
# @param color [Api::Color] default white
|
70
|
+
# @param size [Size] of font, default 14px
|
71
|
+
# @return [void]
|
72
|
+
def debug_text_world(text, point:, color: nil, size: 14)
|
73
|
+
queue_debug_command Api::DebugCommand.new(
|
74
|
+
draw: Api::DebugDraw.new(
|
75
|
+
text: [
|
76
|
+
Api::DebugText.new(
|
77
|
+
text:,
|
78
|
+
world_pos: point,
|
79
|
+
color:,
|
80
|
+
size:
|
81
|
+
)
|
82
|
+
]
|
83
|
+
)
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
40
87
|
# Draws a line between two Api::Point's for color
|
41
88
|
# @param p0 [Api::Point] the first point
|
42
89
|
# @param p1 [Api::Point] the second point
|
@@ -63,8 +110,8 @@ module Sc2
|
|
63
110
|
# # Draws a box on structure placement grid
|
64
111
|
# debug_draw_box(point: unit.pos, radius: unit.footprint_radius)
|
65
112
|
#
|
66
|
-
#
|
67
|
-
#
|
113
|
+
# @note Api::Color RGB is broken for this command. Will use min(r,b)
|
114
|
+
# @note Z index is elevated 0.02 so the line is visible and doesn't clip through terrain
|
68
115
|
# @param point [Api::Point]
|
69
116
|
# @param radius [Float] default one tile wide, 1.0
|
70
117
|
# @param color [Api::Color] default white. min(r,b) is used for both r&b
|
@@ -74,8 +121,8 @@ module Sc2
|
|
74
121
|
draw: Api::DebugDraw.new(
|
75
122
|
boxes: [
|
76
123
|
Api::DebugBox.new(
|
77
|
-
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.
|
78
|
-
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.
|
124
|
+
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.02),
|
125
|
+
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.02),
|
79
126
|
color:
|
80
127
|
)
|
81
128
|
]
|
@@ -7,7 +7,7 @@ module Sc2
|
|
7
7
|
class Player
|
8
8
|
# Holds map and geography helper functions
|
9
9
|
class Geometry
|
10
|
-
# @!attribute
|
10
|
+
# @!attribute bot
|
11
11
|
# @return [Sc2::Player] player with active connection
|
12
12
|
attr_accessor :bot
|
13
13
|
|
@@ -72,6 +72,7 @@ module Sc2
|
|
72
72
|
# Each value in [row][column] holds a boolean value represented as an integer
|
73
73
|
# It does not say whether a position is occupied by another building.
|
74
74
|
# One pixel covers one whole block. Rounds fractionated positions down.
|
75
|
+
# @return [Numo::Bit]
|
75
76
|
def parsed_placement_grid
|
76
77
|
if @parsed_placement_grid.nil?
|
77
78
|
image_data = bot.game_info.start_raw.placement_grid
|
@@ -83,6 +84,7 @@ module Sc2
|
|
83
84
|
end
|
84
85
|
|
85
86
|
# Returns a grid where ony the expo locations are marked
|
87
|
+
# @return [Numo::Bit]
|
86
88
|
def expo_placement_grid
|
87
89
|
if @expo_placement_grid.nil?
|
88
90
|
@expo_placement_grid = Numo::Bit.zeros(map_height, map_width)
|
@@ -97,6 +99,7 @@ module Sc2
|
|
97
99
|
end
|
98
100
|
|
99
101
|
# Returns a grid where powered locations are marked true
|
102
|
+
# @return [Numo::Bit]
|
100
103
|
def parsed_power_grid
|
101
104
|
# Cache for based on power unit tags
|
102
105
|
cache_key = bot.power_sources.map(&:tag).sort.hash
|
@@ -256,6 +259,12 @@ module Sc2
|
|
256
259
|
parsed_terrain_height[y.to_i, x.to_i]
|
257
260
|
end
|
258
261
|
|
262
|
+
# Returns the terrain height (z) at position x and y for a point
|
263
|
+
# @return [Float] z axis position between -16 and 16
|
264
|
+
def terrain_height_for_pos(position)
|
265
|
+
terrain_height(x: position.x, y: position.y)
|
266
|
+
end
|
267
|
+
|
259
268
|
# Returns a parsed terrain_height from bot.game_info.start_raw.
|
260
269
|
# Each value in [row][column] holds a float value which is the z height
|
261
270
|
# @return [Numo::SFloat] Numo array
|
@@ -399,12 +408,25 @@ module Sc2
|
|
399
408
|
output_grid
|
400
409
|
end
|
401
410
|
|
411
|
+
# Returns own 2d start position as set by initial camera
|
412
|
+
# This differs from position of first base structure
|
413
|
+
# @return [Api::Point2D]
|
414
|
+
def start_position
|
415
|
+
@start_position ||= bot.observation.raw_data.player.camera
|
416
|
+
end
|
417
|
+
|
418
|
+
# Returns the enemy 2d start position
|
419
|
+
# @return [Api::Point2D]
|
420
|
+
def enemy_start_position
|
421
|
+
bot.game_info.start_raw.start_locations.first
|
422
|
+
end
|
423
|
+
|
402
424
|
# Gets expos and surrounding minerals
|
403
425
|
# The index is a build location for an expo and the value is a UnitGroup, which has minerals and geysers
|
404
426
|
# @example
|
405
|
-
# random_expo = expansions.keys.sample #=> Point2D
|
427
|
+
# random_expo = geo.expansions.keys.sample #=> Point2D
|
406
428
|
# expo_resources = geo.expansions[random_expo] #=> UnitGroup
|
407
|
-
# alive_minerals = expo_resources.minerals
|
429
|
+
# alive_minerals = expo_resources.minerals & neutral.minerals
|
408
430
|
# geysers = expo_resources.geysers
|
409
431
|
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
410
432
|
def expansions
|
@@ -503,24 +525,77 @@ module Sc2
|
|
503
525
|
end
|
504
526
|
|
505
527
|
# Returns a slice of #expansions where a base hasn't been built yet
|
528
|
+
# The has index is a build position and the value is a UnitGroup of resources for the base
|
506
529
|
# @example
|
507
530
|
# # Lets find the nearest unoccupied expo
|
508
531
|
# expo_pos = expansions_unoccupied.keys.min { |p2d| p2d.distance_to(structures.hq.first) }
|
509
532
|
# # What minerals/geysers does it have?
|
510
533
|
# puts expansions_unoccupied[expo_pos].minerals # or expansions[expo_pos]... => UnitGroup
|
511
534
|
# puts expansions_unoccupied[expo_pos].geysers # or expansions[expo_pos]... => UnitGroup
|
512
|
-
# @return [Hash<Api::Point2D
|
535
|
+
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
513
536
|
def expansions_unoccupied
|
514
537
|
taken_bases = bot.structures.hq.map { |hq| hq.pos.to_p2d } + bot.enemy.structures.hq.map { |hq| hq.pos.to_p2d }
|
515
538
|
remaining_points = expansion_points - taken_bases
|
516
539
|
expansions.slice(*remaining_points)
|
517
540
|
end
|
518
541
|
|
542
|
+
# Gets minerals for a base or base position
|
543
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
544
|
+
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
545
|
+
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)
|
549
|
+
end
|
550
|
+
|
551
|
+
# Gets geysers for a base or base position
|
552
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
553
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
554
|
+
def geysers_for_base(base)
|
555
|
+
resources_for_base(base).geysers
|
556
|
+
end
|
557
|
+
|
558
|
+
# @private
|
559
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
560
|
+
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
561
|
+
private def resources_for_base(base)
|
562
|
+
pos = base.is_a?(Api::Unit) ? base.pos : base
|
563
|
+
|
564
|
+
# If we have a base setup for this exact position, use it
|
565
|
+
if expansions.has_key?(pos)
|
566
|
+
return expansions[pos]
|
567
|
+
end
|
568
|
+
|
569
|
+
# Tolerance for misplaced base: Find the nearest base to this position
|
570
|
+
pos = expansion_points.min_by { |p| p.distance_to(pos) }
|
571
|
+
|
572
|
+
expansions[pos]
|
573
|
+
end
|
574
|
+
|
575
|
+
# Gets gasses for a base or base position
|
576
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
577
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
578
|
+
def gas_for_base(base)
|
579
|
+
# No gas structures at all yet, return nothing
|
580
|
+
return UnitGroup.new if bot.structures.gas.size.zero?
|
581
|
+
|
582
|
+
geysers = geysers_for_base(base)
|
583
|
+
|
584
|
+
# Mineral-only base, return nothing
|
585
|
+
return UnitGroup.new if geysers.size == 0
|
586
|
+
|
587
|
+
# Loop and collect gasses places exactly on-top of geysers
|
588
|
+
bot.structures.gas.select do |gas|
|
589
|
+
geysers.any? { |geyser| geyser.pos.to_p2d.eql?(gas.pos.to_p2d) }
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
519
593
|
# Gets buildable point grid for squares of size, i.e. 3 = 3x3 placements
|
520
594
|
# Uses pathing grid internally, to ignore taken positions
|
521
595
|
# Does not query the api and is generally fast.
|
522
596
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
523
|
-
# @param on_creep [Boolean] whether this build
|
597
|
+
# @param on_creep [Boolean] whether this build location should be on creep
|
598
|
+
# @return [Array<Array<(Float, Float)>>] Array of [x,y] tuples
|
524
599
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
525
600
|
length = 1 if length < 1
|
526
601
|
@_build_coordinates ||= {}
|
@@ -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,53 @@ 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
|
+
|
33
80
|
# An array of Protoss power sources, which have a point, radius and unit tag
|
34
81
|
# @!attribute power_sources
|
35
82
|
# @return [Array<Api::PowerSource>] an array of power sources
|
@@ -47,10 +94,10 @@ module Sc2
|
|
47
94
|
|
48
95
|
# Event-driven unit groups ---
|
49
96
|
|
97
|
+
# @!attribute event_units_created
|
50
98
|
# Units created since last frame (visible only, units not structures)
|
51
99
|
# Read this on_step. Alternative to callback on_unit_created
|
52
|
-
#
|
53
|
-
# @!attribute event_units_created
|
100
|
+
# @note Morphed units should watch #event_units_type_changed
|
54
101
|
# @return [Sc2::UnitGroup] group of created units
|
55
102
|
attr_accessor :event_units_created
|
56
103
|
|
@@ -107,6 +154,13 @@ module Sc2
|
|
107
154
|
data.abilities[ability_id]
|
108
155
|
end
|
109
156
|
|
157
|
+
# Returns static [Api::UpgradeData] for an upgrade id
|
158
|
+
# @param upgrade_id [Integer] Api::UpgradeId::*
|
159
|
+
# @return [Api::UpgradeData]
|
160
|
+
def upgrade_data(upgrade_id)
|
161
|
+
data.upgrades[upgrade_id]
|
162
|
+
end
|
163
|
+
|
110
164
|
# Checks unit data for an attribute value
|
111
165
|
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
112
166
|
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::Mechanical or :Mechanical
|
@@ -141,17 +195,13 @@ module Sc2
|
|
141
195
|
def subtract_cost(unit_type_id)
|
142
196
|
unit_type_data = unit_data(unit_type_id)
|
143
197
|
|
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
198
|
@spent_minerals += unit_type_data.mineral_cost
|
150
199
|
@spent_vespene += unit_type_data.vespene_cost
|
151
|
-
@spent_supply +=
|
200
|
+
@spent_supply += unit_type_data.food_required
|
152
201
|
end
|
153
202
|
|
154
203
|
# Checks whether you have the resources to construct quantity of unit type
|
204
|
+
# @return [Boolean]
|
155
205
|
def can_afford?(unit_type_id:, quantity: 1)
|
156
206
|
unit_type_data = unit_data(unit_type_id)
|
157
207
|
return false if unit_type_data.nil?
|
@@ -178,6 +228,25 @@ module Sc2
|
|
178
228
|
true
|
179
229
|
end
|
180
230
|
|
231
|
+
# Checks whether you have the resources to
|
232
|
+
# @return [Boolean]
|
233
|
+
def can_afford_upgrade?(upgrade_id)
|
234
|
+
unit_type_data = upgrade_data(upgrade_id)
|
235
|
+
return false if unit_type_data.nil?
|
236
|
+
|
237
|
+
mineral_cost = unit_type_data.mineral_cost
|
238
|
+
if common.minerals - spent_minerals < mineral_cost
|
239
|
+
return false # not enough minerals
|
240
|
+
end
|
241
|
+
|
242
|
+
vespene_cost = unit_type_data.vespene_cost
|
243
|
+
if common.vespene - spent_vespene < vespene_cost
|
244
|
+
return false # you require more vespene gas
|
245
|
+
end
|
246
|
+
|
247
|
+
true
|
248
|
+
end
|
249
|
+
|
181
250
|
private
|
182
251
|
|
183
252
|
# @private
|
data/lib/sc2ai/player.rb
CHANGED
@@ -97,7 +97,7 @@ module Sc2
|
|
97
97
|
@difficulty = difficulty
|
98
98
|
@ai_build = ai_build
|
99
99
|
@realtime = false
|
100
|
-
@step_count =
|
100
|
+
@step_count = 2
|
101
101
|
|
102
102
|
@enable_feature_layer = false
|
103
103
|
@interface_options = {}
|
@@ -201,7 +201,7 @@ module Sc2
|
|
201
201
|
|
202
202
|
# Override to customize initialization
|
203
203
|
# Alias of before_join
|
204
|
-
# You can enable_feature_layer=true, set
|
204
|
+
# You can enable_feature_layer=true, set step_count, define
|
205
205
|
# @example
|
206
206
|
# def configure
|
207
207
|
# step_count = 4 # Update less frequently
|
@@ -490,6 +490,8 @@ module Sc2
|
|
490
490
|
def started
|
491
491
|
# Calculate expansions
|
492
492
|
geo.expansions
|
493
|
+
# Set our start position base on camera
|
494
|
+
geo.start_position
|
493
495
|
end
|
494
496
|
|
495
497
|
# Moves emulation ahead and calls back #on_step
|
@@ -498,9 +500,7 @@ module Sc2
|
|
498
500
|
# Sc2.logger.debug "#{self.class} step_forward"
|
499
501
|
|
500
502
|
unless @realtime
|
501
|
-
|
502
|
-
num_steps = 1
|
503
|
-
@api.step(num_steps)
|
503
|
+
@api.step(@step_count)
|
504
504
|
end
|
505
505
|
|
506
506
|
refresh_state
|
@@ -610,9 +610,9 @@ module Sc2
|
|
610
610
|
# ##TODO: perfect loop implementation
|
611
611
|
# observation has an optional param game_loop and will only return once that step is reached (blocking).
|
612
612
|
# without it, it returns things as they are.
|
613
|
-
# broadly, i think this is what it should be doing, with
|
613
|
+
# broadly, i think this is what it should be doing, with step_count being minimum of 1, so no zero-steps occur.
|
614
614
|
# @example
|
615
|
-
# desired_game_loop = current_game_loop +
|
615
|
+
# desired_game_loop = current_game_loop + step_count
|
616
616
|
# response = client.observation(game_loop: desired_game_loop)
|
617
617
|
#
|
618
618
|
# if response.game_loop > desired_game_loop {
|
@@ -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 ---
|
@@ -1,6 +1,15 @@
|
|
1
1
|
module Api
|
2
2
|
# Adds additional functionality to message object Api::Point
|
3
3
|
module PointExtension
|
4
|
+
# @private
|
5
|
+
def hash
|
6
|
+
[x, y, z].hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def eql?(other)
|
10
|
+
self.class == other.class && hash == other.hash
|
11
|
+
end
|
12
|
+
|
4
13
|
# Creates a Point2D using x and y
|
5
14
|
# @return [Api::Point2D]
|
6
15
|
def to_p2d
|
@@ -13,6 +22,9 @@ module Api
|
|
13
22
|
# @example
|
14
23
|
# Api::Point[1,2,3] # Where x is 1.0, y is 2.0 and z is 3.0
|
15
24
|
# @return [Api::Point]
|
25
|
+
# @param [Float] x
|
26
|
+
# @param [Float] y
|
27
|
+
# @param [Float] z
|
16
28
|
def [](x, y, z)
|
17
29
|
Api::Point.new(x: x, y: y, z: z)
|
18
30
|
end
|
@@ -20,4 +32,4 @@ module Api
|
|
20
32
|
end
|
21
33
|
end
|
22
34
|
Api::Point.include Api::PointExtension
|
23
|
-
Api::Point.
|
35
|
+
Api::Point.extend Api::PointExtension::ClassMethods
|
@@ -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]
|
@@ -37,9 +37,8 @@ module Api
|
|
37
37
|
# Checks unit data for an attribute value
|
38
38
|
# @return [Boolean] whether unit has attribute
|
39
39
|
# @example
|
40
|
-
# has_attribute?(Api::
|
41
|
-
# has_attribute?(
|
42
|
-
# has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
|
40
|
+
# unit.has_attribute?(Api::Attribute::Mechanical)
|
41
|
+
# unit.has_attribute?(:Mechanical)
|
43
42
|
def has_attribute?(attribute)
|
44
43
|
attributes.include? attribute
|
45
44
|
end
|
@@ -179,6 +178,26 @@ module Api
|
|
179
178
|
action(ability_id: Api::AbilityId::SMART, target:, queue_command:)
|
180
179
|
end
|
181
180
|
|
181
|
+
# Shorthand for performing action MOVE
|
182
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
183
|
+
# @param queue_command [Boolean] shift+command
|
184
|
+
def move(target:, queue_command: false)
|
185
|
+
action(ability_id: Api::AbilityId::MOVE, target:, queue_command:)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Shorthand for performing action STOP
|
189
|
+
# @param queue_command [Boolean] shift+command
|
190
|
+
def stop(queue_command: false)
|
191
|
+
action(ability_id: Api::AbilityId::STOP, queue_command:)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Shorthand for performing action HOLDPOSITION
|
195
|
+
# @param queue_command [Boolean] shift+command
|
196
|
+
def hold(queue_command: false)
|
197
|
+
action(ability_id: Api::AbilityId::HOLDPOSITION, queue_command:)
|
198
|
+
end
|
199
|
+
alias_method :hold_position, :hold
|
200
|
+
|
182
201
|
# Shorthand for performing action ATTACK
|
183
202
|
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
184
203
|
# @param queue_command [Boolean] shift+command
|
@@ -213,10 +232,18 @@ module Api
|
|
213
232
|
|
214
233
|
# Issues repair command on target
|
215
234
|
# @param target [Api::Unit, Integer] is a unit or unit tag
|
235
|
+
# @param queue_command [Boolean] shift+command
|
216
236
|
def repair(target:, queue_command: false)
|
217
237
|
action(ability_id: Api::AbilityId::EFFECT_REPAIR, target:, queue_command:)
|
218
238
|
end
|
219
239
|
|
240
|
+
# Research a specific upgrade
|
241
|
+
# @param upgrade_id [Integer] Api::UnitTypeId the unit type which will do the creation
|
242
|
+
# @param queue_command [Boolean] shift+command
|
243
|
+
def research(upgrade_id:, queue_command: false)
|
244
|
+
@bot.research(units: self, upgrade_id:, queue_command:)
|
245
|
+
end
|
246
|
+
|
220
247
|
# @!endgroup Actions
|
221
248
|
#
|
222
249
|
# Debug ----
|
@@ -224,6 +251,7 @@ module Api
|
|
224
251
|
# Draws a placement outline
|
225
252
|
# @param color [Api::Color] optional api color, default white
|
226
253
|
# @return [void]
|
254
|
+
# noinspection RubyArgCount
|
227
255
|
def debug_draw_placement(color = nil)
|
228
256
|
# Slightly elevate the Z position so that the line doesn't clip into the terrain at same Z level
|
229
257
|
z_elevated = pos.z + 0.01
|
@@ -432,6 +460,12 @@ module Api
|
|
432
460
|
build_progress == 1.0 # standard:disable Lint/FloatComparison
|
433
461
|
end
|
434
462
|
|
463
|
+
# Returns true if build progress is < 100%
|
464
|
+
# @return [Boolean]
|
465
|
+
def in_progress?
|
466
|
+
!is_completed?
|
467
|
+
end
|
468
|
+
|
435
469
|
# Convenience functions ---
|
436
470
|
|
437
471
|
# TERRAN Convenience functions ---
|
@@ -444,7 +478,7 @@ module Api
|
|
444
478
|
|
445
479
|
# Returns whether the structure has a reactor add-on
|
446
480
|
# @return [Boolean] if the unit has a reactor attached
|
447
|
-
def has_reactor
|
481
|
+
def has_reactor?
|
448
482
|
Sc2::UnitGroup::TYPE_REACTOR.include?(add_on&.unit_type)
|
449
483
|
end
|
450
484
|
|
@@ -455,26 +489,46 @@ module Api
|
|
455
489
|
# # Get the actual tech-lab with #add_on
|
456
490
|
# sp.add_on.research ...
|
457
491
|
# @return [Boolean] if the unit has a tech lab attached
|
458
|
-
def has_tech_lab
|
492
|
+
def has_tech_lab?
|
459
493
|
Sc2::UnitGroup::TYPE_TECHLAB.include?(add_on&.unit_type)
|
460
494
|
end
|
461
495
|
|
462
496
|
# For Terran builds a tech lab add-on on the current structure
|
463
497
|
# @return [void]
|
464
|
-
def build_reactor
|
465
|
-
|
498
|
+
def build_reactor(queue_command: false)
|
499
|
+
unit_type_id = case unit_type
|
500
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
501
|
+
Api::UnitTypeId::BARRACKSREACTOR
|
502
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
503
|
+
Api::UnitTypeId::FACTORYREACTOR
|
504
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
505
|
+
Api::UnitTypeId::STARPORTREACTOR
|
506
|
+
end
|
507
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
466
508
|
end
|
467
509
|
|
468
510
|
# For Terran builds a tech lab add-on on the current structure
|
469
511
|
# @return [void]
|
470
|
-
def build_tech_lab
|
471
|
-
|
512
|
+
def build_tech_lab(queue_command: false)
|
513
|
+
unit_type_id = case unit_type
|
514
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
515
|
+
Api::UnitTypeId::BARRACKSTECHLAB
|
516
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
517
|
+
Api::UnitTypeId::FACTORYTECHLAB
|
518
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
519
|
+
Api::UnitTypeId::STARPORTTECHLAB
|
520
|
+
end
|
521
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
472
522
|
end
|
473
523
|
|
524
|
+
# GENERAL Convenience functions ---
|
525
|
+
|
526
|
+
# ...
|
527
|
+
|
474
528
|
private
|
475
529
|
|
476
530
|
# @private
|
477
|
-
# Reduces
|
531
|
+
# Reduces repetition in the is_*action*?(target:) methods
|
478
532
|
def is_performing_ability_on_target?(abilities, target: nil)
|
479
533
|
# Exit if not actioning the ability
|
480
534
|
return false unless is_performing_ability?(abilities)
|
@@ -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
|