sc2ai 0.0.6 → 0.0.8
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/stableid.json +424 -2562
- data/data/versions.json +8 -0
- data/lib/docker_build/Dockerfile.ruby +1 -1
- data/lib/sc2ai/api/ability_id.rb +12 -314
- data/lib/sc2ai/api/buff_id.rb +9 -16
- data/lib/sc2ai/api/data.rb +1 -1
- data/lib/sc2ai/api/tech_tree.rb +35 -0
- data/lib/sc2ai/api/tech_tree_data.rb +14 -9
- data/lib/sc2ai/api/unit_type_id.rb +6 -58
- data/lib/sc2ai/api/upgrade_id.rb +9 -9
- data/lib/sc2ai/connection/requests.rb +10 -7
- data/lib/sc2ai/data.rb +101 -0
- data/lib/sc2ai/local_play/match.rb +1 -3
- data/lib/sc2ai/player/actions.rb +8 -4
- data/lib/sc2ai/player/debug.rb +2 -3
- data/lib/sc2ai/player/game_state.rb +28 -0
- data/lib/sc2ai/player/geometry.rb +44 -39
- data/lib/sc2ai/player/previous_state.rb +1 -0
- data/lib/sc2ai/player/units.rb +25 -37
- data/lib/sc2ai/player.rb +7 -7
- data/lib/sc2ai/protocol/_meta_documentation.rb +18 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +1 -1
- data/lib/sc2ai/protocol/extensions/point_distance.rb +11 -0
- data/lib/sc2ai/protocol/extensions/position.rb +8 -10
- data/lib/sc2ai/protocol/extensions/unit.rb +29 -3
- data/lib/sc2ai/protocol/extensions/unit_type.rb +9 -0
- data/lib/sc2ai/unit_group/action_ext.rb +3 -3
- data/lib/sc2ai/unit_group/filter_ext.rb +22 -7
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/run_example_match.rb.tt +1 -1
- data/sig/sc2ai.rbs +387 -530
- metadata +39 -21
data/lib/sc2ai/api/upgrade_id.rb
CHANGED
@@ -299,14 +299,14 @@ module Api
|
|
299
299
|
DIGGINGCLAWS = 293
|
300
300
|
CARRIERCARRIERCAPACITY = 294
|
301
301
|
CARRIERLEASHRANGEUPGRADE = 295
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
302
|
+
HURRICANETHRUSTERS = 296
|
303
|
+
TEMPESTGROUNDATTACKUPGRADE = 297
|
304
|
+
MICROBIALSHROUD = 298
|
305
|
+
INTERFERENCEMATRIX = 299
|
306
|
+
SUNDERINGIMPACT = 300
|
307
|
+
AMPLIFIEDSHIELDING = 301
|
308
|
+
PSIONICAMPLIFIERS = 302
|
309
|
+
SECRETEDCOATING = 303
|
310
|
+
ENHANCEDSHOCKWAVES = 304
|
311
311
|
end
|
312
312
|
end
|
@@ -279,10 +279,11 @@ module Sc2
|
|
279
279
|
# Advances the game simulation by step_count. Not used in realtime mode.
|
280
280
|
# Only constant step size supported - subsequent requests use cache.
|
281
281
|
def step(step_count = 1)
|
282
|
-
@_cached_request_step ||=
|
282
|
+
@_cached_request_step ||= {}
|
283
|
+
@_cached_request_step[step_count] ||= Api::Request.new(
|
283
284
|
step: Api::RequestStep.new(count: step_count)
|
284
285
|
).to_proto
|
285
|
-
send_request_and_ignore(@_cached_request_step)
|
286
|
+
send_request_and_ignore(@_cached_request_step[step_count])
|
286
287
|
end
|
287
288
|
|
288
289
|
# Additional methods for inspecting game state. Synchronous and must wait on response
|
@@ -291,7 +292,7 @@ module Sc2
|
|
291
292
|
# @param placements [Array<Api::RequestQueryBuildingPlacement>]
|
292
293
|
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
293
294
|
# @return [Api::ResponseQuery]
|
294
|
-
def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements:
|
295
|
+
def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: false)
|
295
296
|
send_request_for query: Api::RequestQuery.new(
|
296
297
|
pathing:,
|
297
298
|
abilities:,
|
@@ -316,7 +317,7 @@ module Sc2
|
|
316
317
|
# @param queries [Array<Api::RequestQueryAvailableAbilities>] one or more pathing queries
|
317
318
|
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
318
319
|
# @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
|
319
|
-
def query_abilities(queries, ignore_resource_requirements:
|
320
|
+
def query_abilities(queries, ignore_resource_requirements: false)
|
320
321
|
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
321
322
|
|
322
323
|
response = send_request_for query: Api::RequestQuery.new(
|
@@ -330,7 +331,8 @@ module Sc2
|
|
330
331
|
# @param unit_tags [Array<Integer>] an array of unit tags or a single tag
|
331
332
|
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
332
333
|
# @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
|
333
|
-
def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements:
|
334
|
+
def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: false)
|
335
|
+
return [] if unit_tags.nil?
|
334
336
|
queries = []
|
335
337
|
unit_tags = [unit_tags] unless unit_tags.is_a? Array
|
336
338
|
unit_tags.each do |unit_tag|
|
@@ -345,13 +347,14 @@ module Sc2
|
|
345
347
|
# and can just return an array of ability ids.
|
346
348
|
# Note: Querying single units are expensive and should be batched with #query_abilities_for_unit_tags
|
347
349
|
# @param unit [Api::Unit, Integer] a unit or a tag.
|
348
|
-
|
350
|
+
# @return [Array<Integer>] array of ability ids
|
351
|
+
def query_ability_ids_for_unit(unit, ignore_resource_requirements: false)
|
349
352
|
tag = unit.is_a?(Api::Unit) ? unit.tag : unit
|
350
353
|
result = query_abilities_for_unit_tags([tag], ignore_resource_requirements:)
|
351
354
|
if result.nil?
|
352
355
|
[]
|
353
356
|
else
|
354
|
-
result.first.abilities
|
357
|
+
result.first.abilities.map(&:ability_id)
|
355
358
|
end
|
356
359
|
end
|
357
360
|
|
data/lib/sc2ai/data.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "api/ability_id"
|
4
|
+
require_relative "api/unit_type_id"
|
5
|
+
require_relative "api/upgrade_id"
|
6
|
+
require_relative "api/buff_id"
|
7
|
+
require_relative "api/effect_id"
|
8
|
+
require_relative "api/tech_tree"
|
9
|
+
|
10
|
+
module Sc2
|
11
|
+
# Holds game data from tech tree and Api::ResponseData
|
12
|
+
# Called once on game start
|
13
|
+
class Data
|
14
|
+
# @!attribute abilities
|
15
|
+
# @return [Hash<Integer, Api::AbilityData]>] AbilitId => AbilityData
|
16
|
+
attr_accessor :abilities
|
17
|
+
# @!attribute units
|
18
|
+
# @return [Hash<Integer, Api::UnitTypeData>] UnitId => UnitData
|
19
|
+
attr_accessor :units
|
20
|
+
# @!attribute upgrades
|
21
|
+
# @return [Hash<Integer, Api::UpgradeData>] UpgradeId => UpgradeData
|
22
|
+
attr_accessor :upgrades
|
23
|
+
# @!attribute buffs
|
24
|
+
# Not particularly useful data. Just use BuffId directly
|
25
|
+
# @return [Hash<Integer, Api::UpgradeData>] BuffId => BuffData
|
26
|
+
attr_accessor :buffs
|
27
|
+
# @!attribute effects
|
28
|
+
# Not particularly useful data. Just use EffectId directly
|
29
|
+
# @return [Hash<Integer, Api::UpgradeData>] EffectId => EffectData
|
30
|
+
attr_accessor :effects
|
31
|
+
|
32
|
+
# @param data [Api::ResponseData]
|
33
|
+
def initialize(data)
|
34
|
+
return unless data
|
35
|
+
|
36
|
+
@abilities = abilities_from_proto(data.abilities)
|
37
|
+
@units = units_from_proto(data.units)
|
38
|
+
@upgrades = upgrades_from_proto(data.upgrades)
|
39
|
+
@buffs = buffs_from_proto(data.buffs)
|
40
|
+
@effects = effects_from_proto(data.effects)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Indexes ability data by ability id
|
46
|
+
# @param abilities [Array<Api::AbilityData>]
|
47
|
+
# @return [Hash<Integer, Api::AbilityData] indexed data
|
48
|
+
def abilities_from_proto(abilities)
|
49
|
+
result = {}
|
50
|
+
|
51
|
+
ability_ids = Api::AbilityId.constants.map { |c| Api::AbilityId.const_get(c) }
|
52
|
+
abilities.each do |a|
|
53
|
+
next if a.ability_id.zero?
|
54
|
+
next if ability_ids.delete(a.ability_id).nil?
|
55
|
+
|
56
|
+
result[a.ability_id] = a
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
# Indexes unit data by id
|
62
|
+
# @param units [Array<Api::UnitTypeData>]
|
63
|
+
# @return [Hash<Integer, Api::UnitTypeData>] indexed data
|
64
|
+
def units_from_proto(units)
|
65
|
+
result = {}
|
66
|
+
units.each do |u|
|
67
|
+
next unless u.available
|
68
|
+
|
69
|
+
result[u.unit_id] = u
|
70
|
+
end
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
# Indexes upgrades data by id
|
75
|
+
# @param upgrades [Array<Api::UpgradeData>]
|
76
|
+
# @return [Hash<Integer, Api::UpgradeData] indexed data
|
77
|
+
def upgrades_from_proto(upgrades)
|
78
|
+
result = {}
|
79
|
+
upgrades.each do |u|
|
80
|
+
result[u.upgrade_id] = u
|
81
|
+
end
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
def effects_from_proto(effects)
|
86
|
+
result = {}
|
87
|
+
effects.each do |e|
|
88
|
+
result[e.effect_id] = e
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
def buffs_from_proto(buffs)
|
94
|
+
result = {}
|
95
|
+
buffs.each do |b|
|
96
|
+
result[b.buff_id] = b
|
97
|
+
end
|
98
|
+
result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -23,8 +23,6 @@ module Sc2
|
|
23
23
|
# end
|
24
24
|
end
|
25
25
|
|
26
|
-
# TODO: DEFINE REALTIME AS A PARAM
|
27
|
-
|
28
26
|
# @!attribute players Sets the Player(s) for the match
|
29
27
|
# @return [Array<Sc2::Player>] an array of assigned players (ai,bots,humans,observers)
|
30
28
|
attr_accessor :players
|
@@ -66,7 +64,7 @@ module Sc2
|
|
66
64
|
connect_players
|
67
65
|
setup_player_hooks
|
68
66
|
|
69
|
-
player_host.create_game(map:, players: @players)
|
67
|
+
player_host.create_game(map:, players: @players, realtime: player_host.realtime)
|
70
68
|
|
71
69
|
api_players.each_with_index do |player, player_index|
|
72
70
|
run_task.async do
|
data/lib/sc2ai/player/actions.rb
CHANGED
@@ -45,9 +45,12 @@ module Sc2
|
|
45
45
|
|
46
46
|
target_pos = nil
|
47
47
|
target_unit_tag = nil
|
48
|
-
|
48
|
+
case target
|
49
|
+
when Api::Point2D
|
49
50
|
target_pos = target
|
50
|
-
|
51
|
+
when Api::Point
|
52
|
+
target_pos = target.to_p2d
|
53
|
+
when Api::Unit
|
51
54
|
target_unit_tag = target.tag
|
52
55
|
else
|
53
56
|
target_unit_tag = target
|
@@ -65,7 +68,7 @@ module Sc2
|
|
65
68
|
|
66
69
|
# Builds target unit type using units as source at optional target
|
67
70
|
# @param units [Array<Integer>,Integer,Api::Unit] can be an Api::Unit, array of Api::Unit#tag or single tag
|
68
|
-
# @param unit_type_id [Integer] Api::UnitTypeId the unit type
|
71
|
+
# @param unit_type_id [Integer] Api::UnitTypeId the unit type you wish to build
|
69
72
|
# @param target [Api::Point2D, Integer, nil] is a unit tag or a Api::Point2D. Nil for addons/orbital
|
70
73
|
# @param queue_command [Boolean] shift+command
|
71
74
|
def build(units:, unit_type_id:, target: nil, queue_command: false)
|
@@ -79,9 +82,10 @@ module Sc2
|
|
79
82
|
|
80
83
|
# Warps in unit type at target (location or pylon) with optional source units (warp gates)
|
81
84
|
# When not specifying the specific warp gate(s), all warpgates will be used
|
82
|
-
# @param unit_type_id [Integer] Api::UnitTypeId the unit type
|
85
|
+
# @param unit_type_id [Integer] Api::UnitTypeId the unit type you wish to build
|
83
86
|
# @param queue_command [Boolean] shift+command
|
84
87
|
# @param target [Api::Point2D, Integer] is a unit tag or a Api::Point2D
|
88
|
+
# @param units [Array<Integer>, Integer, Api::Unit]
|
85
89
|
def warp(unit_type_id:, target:, queue_command:, units: nil)
|
86
90
|
warp_ability = Api::TechTree.unit_type_creation_abilities(
|
87
91
|
source: Api::UnitTypeId::WARPGATE,
|
data/lib/sc2ai/player/debug.rb
CHANGED
@@ -133,7 +133,7 @@ module Sc2
|
|
133
133
|
# Debug draws a sphere at position with a radius in color
|
134
134
|
# @param point [Api::Point]
|
135
135
|
# @param radius [Float] default one tile wide, 1.0
|
136
|
-
# @param color [Api::Color] default white
|
136
|
+
# @param color [Api::Color] default white
|
137
137
|
# @return [void]
|
138
138
|
def debug_draw_sphere(point:, radius: 1.0, color: nil)
|
139
139
|
queue_debug_command Api::DebugCommand.new(
|
@@ -151,14 +151,13 @@ module Sc2
|
|
151
151
|
|
152
152
|
# Other Commands ---
|
153
153
|
|
154
|
-
# Toggles cheat commands on/off (send only once to enable)
|
155
154
|
# @param command [Integer] one of Api::DebugGameState::*
|
156
155
|
# Possible values:
|
157
156
|
# Api::DebugGameState::Show_map
|
158
157
|
# Api::DebugGameState::Control_enemy
|
159
158
|
# Api::DebugGameState::Food
|
160
159
|
# Api::DebugGameState::Free
|
161
|
-
# Api::DebugGameState::
|
160
|
+
# Api::DebugGameState::All_resources
|
162
161
|
# Api::DebugGameState::God
|
163
162
|
# Api::DebugGameState::Minerals
|
164
163
|
# Api::DebugGameState::Gas
|
@@ -106,6 +106,34 @@ module Sc2
|
|
106
106
|
@chats_received || []
|
107
107
|
end
|
108
108
|
|
109
|
+
# @private
|
110
|
+
# @!attribute available_abilities_loop
|
111
|
+
# This is the last loop at which available_abilities was queried.
|
112
|
+
# Used to determine staleness.
|
113
|
+
# @return [Integer]
|
114
|
+
attr_accessor :available_abilities_loop
|
115
|
+
private :available_abilities_loop
|
116
|
+
|
117
|
+
# A Hash by unit tag, holding an array of available ability ids
|
118
|
+
# Synchronously calls RequestQueryAvailableAbilities and caches for this game loop.
|
119
|
+
# @return [Hash<Integer, Array<Integer>>] { unit_tag => [ability_id, ...], ... }
|
120
|
+
def available_abilities
|
121
|
+
# Save/check when last we refreshed abilities
|
122
|
+
if @available_abilities_loop != game_loop
|
123
|
+
|
124
|
+
# Query abilities for all our units + structure tags combined
|
125
|
+
abilities = api.query_abilities_for_unit_tags(units.tags + structures.tags, ignore_resource_requirements: false)
|
126
|
+
# Build the hash by unit tag
|
127
|
+
fresh_available_abilities = {}
|
128
|
+
abilities.each do |row|
|
129
|
+
fresh_available_abilities[row.unit_tag] = row.abilities.map(&:ability_id)
|
130
|
+
end
|
131
|
+
@available_abilities = fresh_available_abilities
|
132
|
+
@available_abilities_loop = game_loop
|
133
|
+
end
|
134
|
+
@available_abilities
|
135
|
+
end
|
136
|
+
|
109
137
|
# An alias for observation.player_common to allow easier access to i.e. common.minerals
|
110
138
|
# @return [Api::PlayerCommon] common info such as minerals, vespene, supply
|
111
139
|
def common
|
@@ -31,6 +31,12 @@ module Sc2
|
|
31
31
|
@map_height ||= bot.game_info.start_raw.map_size.y
|
32
32
|
end
|
33
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]
|
38
|
+
end
|
39
|
+
|
34
40
|
# Returns zero to map_width as range
|
35
41
|
# @return [Range] 0 to map_width
|
36
42
|
def map_range_x
|
@@ -100,8 +106,16 @@ module Sc2
|
|
100
106
|
expansion_points.each do |point|
|
101
107
|
x = point.x.floor
|
102
108
|
y = point.y.floor
|
103
|
-
|
104
|
-
|
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
|
105
119
|
end
|
106
120
|
end
|
107
121
|
@expo_placement_grid
|
@@ -121,40 +135,11 @@ module Sc2
|
|
121
135
|
return result
|
122
136
|
end
|
123
137
|
|
124
|
-
radius = power_source.radius
|
125
|
-
radius_tile = radius.ceil
|
126
|
-
|
127
|
-
# Keep this code-block, should we need to make power sources dynamic again:
|
128
|
-
# START: Dynamic blueprint
|
129
|
-
# # Build a blueprint and mark it everywhere we need to
|
130
|
-
# # Lets mark everything as powered with 1 and then disable non-powered with a 0
|
131
|
-
# blueprint = Numo::Bit.ones(radius.ceil * 2, radius.ceil * 2)
|
132
|
-
# #blueprint[radius_tile, radius_tile] = 0
|
133
|
-
# blueprint[(radius_tile - 1)..radius_tile, (radius_tile - 1)..radius_tile] = 0
|
134
|
-
# # Loop over top-right quadrant of a circle, so we don't have to +/- for distance calcs.
|
135
|
-
# # Additionally, we only measure if in the upper triangle, since the inner is all inside the circle.
|
136
|
-
# # Then apply to all four quadrants.
|
137
|
-
# quadrant_size = radius_tile - 1
|
138
|
-
# point_search_offsets = (0..quadrant_size).to_a.product((0..quadrant_size).to_a)
|
139
|
-
# point_search_offsets.each do |y, x|
|
140
|
-
# next if x < quadrant_size - y # Only upper Triangle
|
141
|
-
#
|
142
|
-
# dist = Math.hypot(x, y)
|
143
|
-
# if dist >= radius
|
144
|
-
# # Mark as outside x4
|
145
|
-
# blueprint[radius_tile + y, radius_tile + x] = 0
|
146
|
-
# blueprint[radius_tile + y, radius_tile - 1 - x] = 0
|
147
|
-
# blueprint[radius_tile - 1 - y, radius_tile + x] = 0
|
148
|
-
# blueprint[radius_tile - 1 - y, radius_tile - 1 - x] = 0
|
149
|
-
# end
|
150
|
-
# end
|
151
|
-
# END: Dynamic blueprint ---
|
152
|
-
|
153
138
|
# Hard-coding this shape for pylon power
|
154
139
|
# 00001111110000
|
140
|
+
# 00011111111000
|
155
141
|
# 00111111111100
|
156
142
|
# 01111111111110
|
157
|
-
# 01111111111110
|
158
143
|
# 11111111111111
|
159
144
|
# 11111111111111
|
160
145
|
# 11111100111111
|
@@ -162,15 +147,35 @@ module Sc2
|
|
162
147
|
# 11111111111111
|
163
148
|
# 11111111111111
|
164
149
|
# 01111111111110
|
165
|
-
# 01111111111110
|
166
150
|
# 00111111111100
|
151
|
+
# 00011111111000
|
167
152
|
# 00001111110000
|
168
|
-
|
169
153
|
# perf: Saving pre-created shape for speed (0.5ms saved) by using hardcode from .to_binary.unpack("C*")
|
170
|
-
blueprint_data = [
|
171
|
-
|
172
|
-
|
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
|
173
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
|
+
|
174
179
|
x_tile = ps.pos.x.floor
|
175
180
|
y_tile = ps.pos.y.floor
|
176
181
|
replace_start_x = (x_tile - radius_tile)
|
@@ -424,7 +429,7 @@ module Sc2
|
|
424
429
|
# This differs from position of first base structure
|
425
430
|
# @return [Api::Point2D]
|
426
431
|
def start_position
|
427
|
-
@start_position ||= bot.observation.raw_data.player.camera
|
432
|
+
@start_position ||= bot.observation.raw_data.player.camera.to_p2d
|
428
433
|
end
|
429
434
|
|
430
435
|
# Returns the enemy 2d start position
|
@@ -751,7 +756,7 @@ module Sc2
|
|
751
756
|
)
|
752
757
|
end
|
753
758
|
nearest = @_build_coordinate_tree[cache_key].nearestk(target.x, target.y, random)
|
754
|
-
return nil if nearest.nil?
|
759
|
+
return nil if nearest.nil? || nearest.empty?
|
755
760
|
|
756
761
|
coordinates[nearest.sample].to_p2d
|
757
762
|
end
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -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
|
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
|
@@ -125,41 +130,17 @@ module Sc2
|
|
125
130
|
|
126
131
|
# An array of Sensor tower rings as per minimap. It has a `pos` and a `radius`
|
127
132
|
# @!attribute power_sources
|
128
|
-
# @return [Array<Api::RadarRing>] an array of
|
133
|
+
# @return [Array<Api::RadarRing>] an array of radar rings sources
|
129
134
|
attr_accessor :radar_rings # not a unit but has a tag
|
130
135
|
|
131
136
|
# @private
|
132
137
|
# @!attribute all_seen_unit_tags
|
133
138
|
# Privately keep track of all seen Unit tags (excl structures) in order to detect new created units
|
134
139
|
attr_accessor :_all_seen_unit_tags
|
140
|
+
private :_all_seen_unit_tags
|
135
141
|
|
136
142
|
# Event-driven unit groups ---
|
137
143
|
|
138
|
-
# @!attribute event_units_created
|
139
|
-
# Units created since last frame (visible only, units not structures)
|
140
|
-
# Read this on_step. Alternative to callback on_unit_created
|
141
|
-
# @note Morphed units should watch #event_units_type_changed
|
142
|
-
# @return [Sc2::UnitGroup] group of created units
|
143
|
-
attr_accessor :event_units_created
|
144
|
-
|
145
|
-
# Units which had their type changed since last frame
|
146
|
-
# Read this on_step. Alternative to callback on_unit_type_changed
|
147
|
-
# @!attribute event_units_type_changed
|
148
|
-
# @return [Sc2::UnitGroup] group effected
|
149
|
-
attr_accessor :event_units_type_changed
|
150
|
-
|
151
|
-
# Structures seen since last frame with building not completed (< 1.0)
|
152
|
-
# Read this on_step. Alternative to callback on_structure_started
|
153
|
-
# @!attribute event_structures_started
|
154
|
-
# @return [Sc2::UnitGroup] a group of structures started
|
155
|
-
attr_accessor :event_structures_started
|
156
|
-
|
157
|
-
# Structures which had their building completed (==1.0) since last frame
|
158
|
-
# Read this on_step. Alternative to callback on_structure_completed
|
159
|
-
# @!attribute event_structures_completed
|
160
|
-
# @return [Sc2::UnitGroup] a group of structures started
|
161
|
-
attr_accessor :event_structures_completed
|
162
|
-
|
163
144
|
# Units and Structures which had their health/shields reduced since last frame
|
164
145
|
# Read this on_step. Alternative to callback on_unit_damaged
|
165
146
|
# @!attribute event_units_damaged
|
@@ -288,6 +269,17 @@ module Sc2
|
|
288
269
|
true
|
289
270
|
end
|
290
271
|
|
272
|
+
# Micro/Unit-Specific ------
|
273
|
+
|
274
|
+
# Returns whether Query Available Ability is true for unit and tag
|
275
|
+
# Queries API if necessary. Uses batching in the background.
|
276
|
+
# @param [Integer] unit_tag
|
277
|
+
# @param [Integer] ability_id
|
278
|
+
# @return [Boolean]
|
279
|
+
def unit_ability_available?(unit_tag:, ability_id:)
|
280
|
+
!!available_abilities[unit_tag]&.include?(ability_id)
|
281
|
+
end
|
282
|
+
|
291
283
|
private
|
292
284
|
|
293
285
|
# @private
|
@@ -299,8 +291,10 @@ module Sc2
|
|
299
291
|
# Clear previous units and prep for categorization
|
300
292
|
@units = UnitGroup.new
|
301
293
|
@structures = UnitGroup.new
|
294
|
+
@placeholders = UnitGroup.new
|
302
295
|
@enemy.units = UnitGroup.new
|
303
296
|
@enemy.structures = UnitGroup.new
|
297
|
+
@enemy.all_units = UnitGroup.new
|
304
298
|
@neutral = UnitGroup.new
|
305
299
|
@effects = observation.raw_data.effects # not a unit
|
306
300
|
@power_sources = observation.raw_data.player.power_sources # not a unit
|
@@ -311,10 +305,6 @@ module Sc2
|
|
311
305
|
@_all_seen_unit_tags ||= Set.new(@units.tags)
|
312
306
|
|
313
307
|
# Event-driven unit groups as callback alternatives
|
314
|
-
@event_units_created = UnitGroup.new
|
315
|
-
@event_structures_started = UnitGroup.new
|
316
|
-
@event_structures_completed = UnitGroup.new
|
317
|
-
@event_units_type_changed = UnitGroup.new
|
318
308
|
@event_units_damaged = UnitGroup.new
|
319
309
|
# @event_units_buffed = UnitGroup.new
|
320
310
|
|
@@ -335,6 +325,8 @@ module Sc2
|
|
335
325
|
# Categorize own units/structures, enemy units/structures, neutral
|
336
326
|
if unit.is_blip
|
337
327
|
@blips[tag] = unit
|
328
|
+
elsif unit.display_type == :Placeholder
|
329
|
+
@placeholders[tag] = unit
|
338
330
|
elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
|
339
331
|
if unit.alliance == own_alliance
|
340
332
|
structure_collection = @structures
|
@@ -342,6 +334,7 @@ module Sc2
|
|
342
334
|
else
|
343
335
|
structure_collection = @enemy.structures
|
344
336
|
unit_collection = @enemy.units
|
337
|
+
@enemy.all_units[tag] = unit
|
345
338
|
end
|
346
339
|
|
347
340
|
unit_data = unit_data(unit.unit_type)
|
@@ -355,7 +348,7 @@ module Sc2
|
|
355
348
|
end
|
356
349
|
|
357
350
|
# Dont parse callbacks on first loop or for neutral units
|
358
|
-
if
|
351
|
+
if !@previous.all_units.nil? &&
|
359
352
|
unit.alliance != :Neutral &&
|
360
353
|
unit.display_type != :Placeholder &&
|
361
354
|
unit.is_blip == false
|
@@ -406,14 +399,11 @@ module Sc2
|
|
406
399
|
|
407
400
|
if unit.is_structure?
|
408
401
|
if unit.build_progress < 1
|
409
|
-
@event_structures_started.add(unit)
|
410
402
|
on_structure_started(unit)
|
411
403
|
else
|
412
|
-
@event_structures_completed.add(unit)
|
413
404
|
on_structure_completed(unit)
|
414
405
|
end
|
415
406
|
else
|
416
|
-
@event_units_created.add(unit)
|
417
407
|
on_unit_created(unit)
|
418
408
|
end
|
419
409
|
@_all_seen_unit_tags.add(unit.tag)
|
@@ -424,7 +414,6 @@ module Sc2
|
|
424
414
|
def issue_existing_unit_callbacks(unit, previous_unit)
|
425
415
|
# Check if a unit type has changed
|
426
416
|
if unit.unit_type != previous_unit.unit_type
|
427
|
-
@event_units_type_changed.add(unit)
|
428
417
|
on_unit_type_changed(unit, previous_unit.unit_type)
|
429
418
|
end
|
430
419
|
|
@@ -437,7 +426,6 @@ module Sc2
|
|
437
426
|
|
438
427
|
if unit.is_structure?
|
439
428
|
if previous_unit.build_progress < 1 && unit.build_progress == 1
|
440
|
-
@event_structures_completed.add(unit)
|
441
429
|
on_structure_completed(unit)
|
442
430
|
end
|
443
431
|
end
|
data/lib/sc2ai/player.rb
CHANGED
@@ -233,7 +233,7 @@ module Sc2
|
|
233
233
|
perform_actions
|
234
234
|
perform_debug_commands # TODO: Detect IS_LADDER? -> unless IS_LADDER?
|
235
235
|
step_forward
|
236
|
-
print "\e[2K#{@
|
236
|
+
print "\e[2K#{game_loop - @previous.game_loop} Steps Took (ms): #{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000}\n\e[1A\r"
|
237
237
|
return @result unless @result.nil?
|
238
238
|
break if @status != :in_game
|
239
239
|
end
|
@@ -329,7 +329,7 @@ module Sc2
|
|
329
329
|
|
330
330
|
# Callback for unit destroyed. Tags might be found in `previous.all_units`
|
331
331
|
# This excludes unknown objects, like projectiles and only shows things the API has "seen" as a unit
|
332
|
-
# Override to use in your bot class or use Player.
|
332
|
+
# Override to use in your bot class or use Player.
|
333
333
|
# @param unit [Api::Unit]
|
334
334
|
# @see Sc2::Player::Units#units_destroyed
|
335
335
|
def on_unit_destroyed(unit)
|
@@ -343,7 +343,7 @@ module Sc2
|
|
343
343
|
|
344
344
|
# Callback for unit type changing.
|
345
345
|
# To detect certain unit creations, you should use this method to watch morphs.
|
346
|
-
# Override to use in your bot class or use Player.
|
346
|
+
# Override to use in your bot class or use Player.
|
347
347
|
# @param unit [Api::Unit]
|
348
348
|
# @param previous_unit_type_id [Integer] Api::UnitTypeId::*
|
349
349
|
def on_unit_type_changed(unit, previous_unit_type_id)
|
@@ -356,13 +356,13 @@ module Sc2
|
|
356
356
|
end
|
357
357
|
|
358
358
|
# Callback for structure building is completed
|
359
|
-
# Override to use in your bot class or use Player.
|
359
|
+
# Override to use in your bot class or use Player.
|
360
360
|
# @param unit [Api::Unit]
|
361
361
|
def on_structure_completed(unit)
|
362
362
|
end
|
363
363
|
|
364
364
|
# Callback for unit (Unit/Structure) taking damage
|
365
|
-
# Override to use in your bot class or use Player.
|
365
|
+
# Override to use in your bot class or use Player.
|
366
366
|
# @param unit [Api::Unit]
|
367
367
|
# @param amount [Integer] of damage
|
368
368
|
def on_unit_damaged(unit, amount)
|
@@ -549,7 +549,7 @@ module Sc2
|
|
549
549
|
# Having loaded all the necessities for the current state...
|
550
550
|
# If we're on the first frame of the game, say previous state and current are the same
|
551
551
|
# This is better than having a bunch of random zero and nil values
|
552
|
-
@previous.reset(self) if
|
552
|
+
@previous.reset(self) if @previous.all_units.nil?
|
553
553
|
|
554
554
|
# TODO: remove @events attributes if we don't use them for performance gains
|
555
555
|
# Actions performed and errors (only if implemented)
|
@@ -586,7 +586,7 @@ module Sc2
|
|
586
586
|
self.game_info = @api.game_info
|
587
587
|
end
|
588
588
|
|
589
|
-
#
|
589
|
+
# Enemy -----------------------
|
590
590
|
|
591
591
|
# If you're random, best to set #race to match after launched
|
592
592
|
def set_race_for_random
|