sc2ai 0.0.0.pre → 0.0.2
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 -0
- data/data/data_readable.json +22842 -0
- data/data/sc2ai/protocol/common.proto +59 -0
- data/data/sc2ai/protocol/data.proto +120 -0
- data/data/sc2ai/protocol/debug.proto +127 -0
- data/data/sc2ai/protocol/error.proto +221 -0
- data/data/sc2ai/protocol/query.proto +55 -0
- data/data/sc2ai/protocol/raw.proto +202 -0
- data/data/sc2ai/protocol/sc2api.proto +718 -0
- data/data/sc2ai/protocol/score.proto +108 -0
- data/data/sc2ai/protocol/spatial.proto +115 -0
- data/data/sc2ai/protocol/ui.proto +145 -0
- data/data/setup/setup.SC2Map +0 -0
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +35730 -0
- data/data/versions.json +554 -0
- data/exe/sc2ai +35 -0
- data/lib/docker_build/Dockerfile.ruby +74 -0
- data/lib/docker_build/docker-compose-base-image.yml +10 -0
- data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
- data/lib/sc2ai/api/ability_id.rb +1644 -0
- data/lib/sc2ai/api/buff_id.rb +306 -0
- data/lib/sc2ai/api/data.rb +101 -0
- data/lib/sc2ai/api/effect_id.rb +20 -0
- data/lib/sc2ai/api/tech_tree.rb +83 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
- data/lib/sc2ai/api/unit_type_id.rb +2022 -0
- data/lib/sc2ai/api/upgrade_id.rb +310 -0
- data/lib/sc2ai/cli/cli.rb +175 -0
- data/lib/sc2ai/cli/ladderzip.rb +154 -0
- data/lib/sc2ai/cli/new.rb +88 -0
- data/lib/sc2ai/configuration.rb +145 -0
- data/lib/sc2ai/connection/connection_listener.rb +30 -0
- data/lib/sc2ai/connection/requests.rb +417 -0
- data/lib/sc2ai/connection/status_listener.rb +15 -0
- data/lib/sc2ai/connection.rb +146 -0
- data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
- data/lib/sc2ai/local_play/client.rb +159 -0
- data/lib/sc2ai/local_play/client_manager.rb +70 -0
- data/lib/sc2ai/local_play/map_file.rb +48 -0
- data/lib/sc2ai/local_play/match.rb +184 -0
- data/lib/sc2ai/overrides/array.rb +14 -0
- data/lib/sc2ai/overrides/async/process/child.rb +31 -0
- data/lib/sc2ai/overrides/kernel.rb +33 -0
- data/lib/sc2ai/paths.rb +294 -0
- data/lib/sc2ai/player/actions.rb +386 -0
- data/lib/sc2ai/player/debug.rb +224 -0
- data/lib/sc2ai/player/game_state.rb +131 -0
- data/lib/sc2ai/player/geometry.rb +766 -0
- data/lib/sc2ai/player/previous_state.rb +49 -0
- data/lib/sc2ai/player/units.rb +337 -0
- data/lib/sc2ai/player.rb +661 -0
- data/lib/sc2ai/ports.rb +152 -0
- data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
- data/lib/sc2ai/protocol/common_pb.rb +43 -0
- data/lib/sc2ai/protocol/data_pb.rb +47 -0
- data/lib/sc2ai/protocol/debug_pb.rb +56 -0
- data/lib/sc2ai/protocol/error_pb.rb +36 -0
- data/lib/sc2ai/protocol/extensions/color.rb +20 -0
- data/lib/sc2ai/protocol/extensions/point.rb +23 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
- data/lib/sc2ai/protocol/extensions/position.rb +202 -0
- data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
- data/lib/sc2ai/protocol/query_pb.rb +47 -0
- data/lib/sc2ai/protocol/raw_pb.rb +57 -0
- data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
- data/lib/sc2ai/protocol/score_pb.rb +40 -0
- data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
- data/lib/sc2ai/protocol/ui_pb.rb +56 -0
- data/lib/sc2ai/unit_group/action_ext.rb +74 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
- data/lib/sc2ai/unit_group.rb +277 -0
- data/lib/sc2ai/version.rb +2 -1
- data/lib/sc2ai.rb +93 -2
- data/lib/templates/ladderzip/bin/ladder.tt +23 -0
- data/lib/templates/new/.ladderignore +20 -0
- data/lib/templates/new/Gemfile.tt +7 -0
- data/lib/templates/new/api/common.proto +59 -0
- data/lib/templates/new/api/data.proto +120 -0
- data/lib/templates/new/api/debug.proto +127 -0
- data/lib/templates/new/api/error.proto +221 -0
- data/lib/templates/new/api/query.proto +55 -0
- data/lib/templates/new/api/raw.proto +202 -0
- data/lib/templates/new/api/sc2api.proto +718 -0
- data/lib/templates/new/api/score.proto +108 -0
- data/lib/templates/new/api/spatial.proto +115 -0
- data/lib/templates/new/api/ui.proto +145 -0
- data/lib/templates/new/boot.rb.tt +6 -0
- data/lib/templates/new/my_bot.rb.tt +23 -0
- data/lib/templates/new/run_example_match.rb.tt +14 -0
- data/sc2ai.gemspec +80 -0
- metadata +344 -13
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../connection/status_listener"
|
4
|
+
|
5
|
+
module Sc2
|
6
|
+
# Runs a match using a map and player configuration
|
7
|
+
class Match
|
8
|
+
include Sc2::Connection::StatusListener
|
9
|
+
|
10
|
+
# Callback when game status changes
|
11
|
+
def on_status_change(status)
|
12
|
+
Sc2.logger.debug { "Status from Match: #{status}" }
|
13
|
+
|
14
|
+
# if status == :ended
|
15
|
+
# # Go through each player, looking for result if we don't have one.
|
16
|
+
# api_players.each do |player|
|
17
|
+
# Sc2.logger.debug { "TODO: Get results for players" }
|
18
|
+
# # result = player.result
|
19
|
+
# Sc2.logger.debug { "Leaving Game and Disconnecting players" }
|
20
|
+
# player.leave_game
|
21
|
+
# player.disconnect
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO: DEFINE REALTIME AS A PARAM
|
27
|
+
|
28
|
+
# @!attribute players Sets the Player(s) for the match
|
29
|
+
# @return [Array<Sc2::Player>] an array of assigned players (ai,bots,humans,observers)
|
30
|
+
attr_accessor :players
|
31
|
+
|
32
|
+
# @!attribute map Sets the Map for the match
|
33
|
+
# @return [Sc2::MapFile] the Map for the match
|
34
|
+
attr_reader :map
|
35
|
+
|
36
|
+
# @param players [Array<Sc2::Player>]
|
37
|
+
# @param map [String, Sc2::MapFile] String path or map name, or Map
|
38
|
+
# @return [Sc2::Match]
|
39
|
+
def initialize(players:, map: nil)
|
40
|
+
@players = players || []
|
41
|
+
@map = if map.is_a?(String)
|
42
|
+
MapFile.new(map.to_s)
|
43
|
+
else
|
44
|
+
map
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validates a runnable match and raises an error if invalid
|
49
|
+
# @return [void]
|
50
|
+
# @raise [Sc2:Error]
|
51
|
+
def validate
|
52
|
+
@players.select! { |player| player.is_a?(Player) }
|
53
|
+
raise Error, "player count greater than 1 expected" unless @players.length >= 2
|
54
|
+
|
55
|
+
raise Error, "invalid map" if !@map.is_a?(MapFile) || @map.path.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Connects players to instances, creates a game and joins everyone to play!
|
59
|
+
# @return [void]
|
60
|
+
def run
|
61
|
+
validate
|
62
|
+
Sc2.logger.debug { "Connecting players to client..." }
|
63
|
+
|
64
|
+
# Holds the game process and finishes when a status triggers it to end
|
65
|
+
Async do |run_task|
|
66
|
+
connect_players
|
67
|
+
setup_player_hooks
|
68
|
+
|
69
|
+
player_host.create_game(map:, players: @players)
|
70
|
+
|
71
|
+
api_players.each_with_index do |player, player_index|
|
72
|
+
run_task.async do
|
73
|
+
player.join_game(
|
74
|
+
server_host: ClientManager.get(player_index).host,
|
75
|
+
port_config:
|
76
|
+
)
|
77
|
+
|
78
|
+
result = player.play
|
79
|
+
Sc2.logger.debug { "Player(#{player_index}) Result: #{result}" }
|
80
|
+
autosave_replay(player)
|
81
|
+
ensure
|
82
|
+
Sc2.logger.debug { "Game over, disconnect players." }
|
83
|
+
# Suppress interrupt errors #$stderr.reopen File.new(File::NULL, "w")
|
84
|
+
player.disconnect
|
85
|
+
ClientManager.stop(player_index) # unless keep_clients_alive
|
86
|
+
end
|
87
|
+
end
|
88
|
+
rescue
|
89
|
+
# no op - clean exit from game may cause ws disconnection error
|
90
|
+
end.wait
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Saves the replay from the player's perspective.
|
98
|
+
# Requires active client and connection.
|
99
|
+
def autosave_replay(player)
|
100
|
+
safe_player_name = player.name.gsub(/\s*[^A-Za-z0-9.-]\s*/, "_").downcase
|
101
|
+
|
102
|
+
response = player.api.save_replay
|
103
|
+
path = Pathname(Paths.bot_data_replay_dir).join("autosave-#{safe_player_name}.SC2Replay")
|
104
|
+
f = File.new(path, "wb:ASCII-8BIT")
|
105
|
+
f.write(response.data)
|
106
|
+
f.close
|
107
|
+
end
|
108
|
+
|
109
|
+
# Gets a PortConfig
|
110
|
+
# @return [Sc2::PortConfig] port configuration based on players
|
111
|
+
def port_config
|
112
|
+
# Sc2.logger.debug { "Get port config..." }
|
113
|
+
# Detect open ports
|
114
|
+
@port_config = Ports.port_config_auto(num_players: api_players.length)
|
115
|
+
rescue
|
116
|
+
# Fall back to increment method
|
117
|
+
@port_config = Ports.port_config_basic(start_port: 5000, num_players: api_players.length)
|
118
|
+
end
|
119
|
+
|
120
|
+
# def prepare_clients
|
121
|
+
# Async do |task|
|
122
|
+
# api_players.each_with_index do |player, i|
|
123
|
+
# Sc2.logger.debug { "Obtain client for player #{i}: #{player.class}, #{player.name}"
|
124
|
+
# ClientManager.obtain(i)
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
|
129
|
+
# Gets a Sc2 client from Sc2::ClientManager and connects them
|
130
|
+
def connect_players
|
131
|
+
# Depending on number of players, api has different ready states
|
132
|
+
ready_statuses = (api_players.size > 1) ? [:launched] : %i[launched ready]
|
133
|
+
|
134
|
+
# For each player, attempt to connect and retry once after closing Sc2 in worst-case
|
135
|
+
api_players.each_with_index do |player, i|
|
136
|
+
1.upto(max_retires = 2) do |attempt|
|
137
|
+
Sc2.logger.debug { "Player(#{i}) Obtain client for: #{player.class}, #{player.name}" }
|
138
|
+
client = ClientManager.obtain(i)
|
139
|
+
sleep(8) if ENV["SC2PF"] == Paths::PF_WINDOWS
|
140
|
+
Sc2.logger.debug { "Player(#{i}) Connect to client: #{client.host}:#{client.port}" }
|
141
|
+
player.connect(host: client.host, port: client.port)
|
142
|
+
Sc2.logger.debug { "Player(#{i}) Connected." }
|
143
|
+
Sc2.logger.debug { "Player(#{i}) Status is: #{player.status}" }
|
144
|
+
break if ready_statuses.include?(player.status)
|
145
|
+
|
146
|
+
Sc2.logger.debug { "Player(#{i}) Attempt to leave game..." }
|
147
|
+
player.leave_game
|
148
|
+
break if ready_statuses.include?(player.status)
|
149
|
+
|
150
|
+
raise Error, "Player(#{i}) Unable to get client" unless attempt < max_retires
|
151
|
+
|
152
|
+
Sc2.logger.debug { "Player(#{i}) Leave failed. Retry by stopping client..." }
|
153
|
+
player.disconnect
|
154
|
+
ClientManager.stop(i)
|
155
|
+
next
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Configure hooks
|
161
|
+
def setup_player_hooks
|
162
|
+
api_players.each do |player|
|
163
|
+
# Clean surrender locally. Lapizistik taught me bad things.
|
164
|
+
def player.leave_game
|
165
|
+
debug_end_game(end_result: :Surrender)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns a list of players which requires an Sc2 instance
|
171
|
+
# @return [Array<Sc2::Player>] players which requires_client?
|
172
|
+
def api_players
|
173
|
+
players.select(&:requires_client?)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns the first player which requires an Api connection as the host
|
177
|
+
# @return [Sc2::Player] host
|
178
|
+
def player_host
|
179
|
+
raise Sc2::Error, "No host player found. API players are empty." if api_players.nil? || api_players.empty?
|
180
|
+
# noinspection RubyMismatchedReturnType
|
181
|
+
api_players.first
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Array extensions
|
2
|
+
class Array
|
3
|
+
# Turns an Array of Api::Unit into a Sc2::UnitGroup
|
4
|
+
# @return [Sc2::UnitGroup] array converted to a unit group
|
5
|
+
def to_unit_group
|
6
|
+
Sc2::UnitGroup.new(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Creates a Point2D from 0,1 as x,y
|
10
|
+
# @return [Api::Point2D]
|
11
|
+
def to_p2d
|
12
|
+
Api::Point2D.new(x: at(0), y: at(1))
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Overrides process spawn for windows pgroup
|
2
|
+
|
3
|
+
require "async/process"
|
4
|
+
|
5
|
+
module Async
|
6
|
+
module Process
|
7
|
+
class Child
|
8
|
+
def initialize(*args, **options)
|
9
|
+
# Setup a cross-thread notification pipe - nio4r can't monitor pids unfortunately:
|
10
|
+
pipe = ::IO.pipe
|
11
|
+
@input = Async::IO::Generic.new(pipe.first)
|
12
|
+
@output = pipe.last
|
13
|
+
|
14
|
+
@exit_status = nil
|
15
|
+
|
16
|
+
if Gem.win_platform?
|
17
|
+
options[:new_pgroup] = true
|
18
|
+
else
|
19
|
+
options[:pgroup] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
@pid = ::Process.spawn(*args, **options)
|
23
|
+
|
24
|
+
@thread = Thread.new do
|
25
|
+
_, @exit_status = ::Process.wait2(@pid)
|
26
|
+
@output.close
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @private
|
4
|
+
# Patched from: rails/activesupport/lib/active_support/core_ext/kernel/reporting.rb
|
5
|
+
# https://github.com/rails/rails/blob/04972d9b9ef60796dc8f0917817b5392d61fcf09/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L26
|
6
|
+
|
7
|
+
# Kernel extensions
|
8
|
+
module Kernel
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Sets $VERBOSE to +nil+ for the duration of the block and back to its original
|
12
|
+
# value afterwards.
|
13
|
+
#
|
14
|
+
# silence_warnings do
|
15
|
+
# value = noisy_call # no warning voiced
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# noisy_call # warning voiced
|
19
|
+
def silence_warnings(&)
|
20
|
+
with_warnings(nil, &)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets $VERBOSE for the duration of the block and back to its original
|
24
|
+
# value afterwards.
|
25
|
+
# noinspection RubyGlobalVariableNamingConvention
|
26
|
+
def with_warnings(flag)
|
27
|
+
old_verbose = $VERBOSE
|
28
|
+
$VERBOSE = flag
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
$VERBOSE = old_verbose
|
32
|
+
end
|
33
|
+
end
|
data/lib/sc2ai/paths.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tmpdir"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module Sc2
|
7
|
+
# Helps determine common paths to sc2 install dir, executable and maps.
|
8
|
+
# It maintains some semblance of compatibility with python-sc2 config
|
9
|
+
#
|
10
|
+
# ENV['SC2PATH'] can be set manually to Starcraft 2 base directory for Linux
|
11
|
+
# ENV['SC2PF'] can and should be manually set to "WineLinux" when running Wine
|
12
|
+
# Credit to Hannes, Sean and Burny for setting the standard
|
13
|
+
class Paths
|
14
|
+
# Platform name for Windows
|
15
|
+
PF_WINDOWS = "Windows"
|
16
|
+
# Platform name for WSL1
|
17
|
+
PF_WSL1 = "WSL1"
|
18
|
+
# Platform name for WSL2
|
19
|
+
PF_WSL2 = "WSL2"
|
20
|
+
# Platform name macOS
|
21
|
+
PF_DARWIN = "Darwin"
|
22
|
+
# Platform name Linux
|
23
|
+
PF_LINUX = "Linux"
|
24
|
+
# Platform name for Wine
|
25
|
+
PF_WINE = "WineLinux"
|
26
|
+
|
27
|
+
# Where within user directory to locate ExecuteInfo.txt
|
28
|
+
BASE_DIR = {
|
29
|
+
PF_WINDOWS => "C:/Program Files (x86)/StarCraft II",
|
30
|
+
PF_WSL1 => "/mnt/c/Program Files (x86)/StarCraft II",
|
31
|
+
PF_WSL2 => "/mnt/c/Program Files (x86)/StarCraft II",
|
32
|
+
PF_DARWIN => "/Applications/StarCraft II",
|
33
|
+
PF_LINUX => "~/StarCraftII",
|
34
|
+
PF_WINE => "~/.wine/drive_c/Program Files (x86)/StarCraft II"
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# Where within user directory to locate ExecuteInfo.txt
|
38
|
+
EXEC_INFO_PATH = {
|
39
|
+
PF_WINDOWS => "Documents/StarCraft II/ExecuteInfo.txt",
|
40
|
+
PF_WSL1 => "Documents/StarCraft II/ExecuteInfo.txt",
|
41
|
+
PF_WSL2 => "Documents/StarCraft II/ExecuteInfo.txt",
|
42
|
+
PF_DARWIN => "Library/Application Support/Blizzard/StarCraft II/ExecuteInfo.txt",
|
43
|
+
PF_LINUX => nil,
|
44
|
+
PF_WINE => nil
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
# Path helper for finding executable
|
48
|
+
BIN_PATH = {
|
49
|
+
PF_WINDOWS => "SC2_x64.exe",
|
50
|
+
PF_WSL1 => "SC2_x64.exe",
|
51
|
+
PF_WSL2 => "SC2_x64.exe",
|
52
|
+
PF_DARWIN => "SC2.app/Contents/MacOS/SC2",
|
53
|
+
PF_LINUX => "SC2_x64",
|
54
|
+
PF_WINE => "SC2_x64.exe"
|
55
|
+
}.freeze
|
56
|
+
|
57
|
+
# Working directory sub-folder per platform.
|
58
|
+
# Used when spawning client process on some platforms.
|
59
|
+
WORKING_DIR = {
|
60
|
+
PF_WINDOWS => "Support64",
|
61
|
+
PF_WSL1 => "Support64",
|
62
|
+
PF_WSL2 => "Support64",
|
63
|
+
PF_DARWIN => nil,
|
64
|
+
PF_LINUX => nil,
|
65
|
+
PF_WINE => "Support64"
|
66
|
+
}.freeze
|
67
|
+
|
68
|
+
class << self
|
69
|
+
# An array of available platforms
|
70
|
+
# @return [Array<String>] an array of valid platforms
|
71
|
+
def platforms
|
72
|
+
[PF_WINDOWS, PF_WSL1, PF_WSL2, PF_DARWIN, PF_LINUX, PF_WINE]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Bucketed platform names follows convention
|
76
|
+
# Uses ENV['SC2PF'] if configured. This is the only way to set "WineLinux"
|
77
|
+
# @return ["Windows","WSL1","WSL2","Darwin","Linux","WineLinux"] string platform name
|
78
|
+
def platform
|
79
|
+
return ENV.fetch("SC2PF", nil) unless ENV["SC2PF"].to_s.strip.empty?
|
80
|
+
|
81
|
+
if Gem.win_platform?
|
82
|
+
ENV["SC2PF"] = PF_WINDOWS
|
83
|
+
elsif Gem::Platform.local.os == "darwin"
|
84
|
+
ENV["SC2PF"] = PF_DARWIN
|
85
|
+
elsif Gem::Platform.local.os == "linux"
|
86
|
+
ENV["SC2PF"] = PF_LINUX
|
87
|
+
if wsl?
|
88
|
+
ENV["SC2PF"] = wsl2? ? PF_WSL2 : PF_WSL1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
unless ENV.fetch("SC2PF", false)
|
93
|
+
Sc2.logger.warn "unknown platform detected #{Gem::Platform.local.os}. manually set ENV['SC2PF']"
|
94
|
+
end
|
95
|
+
ENV.fetch("SC2PF", nil)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Attempts to auto-detect the user's install directory via ExecuteInfo.txt
|
99
|
+
# SC2PATH is required on WineLinux and Linux
|
100
|
+
# @return [String] path
|
101
|
+
def install_dir
|
102
|
+
if ENV.fetch("SC2PATH", false)
|
103
|
+
# Use if set via environment
|
104
|
+
path = ENV.fetch("SC2PATH")
|
105
|
+
else
|
106
|
+
# Otherwise try read from ExecuteInfo.txt
|
107
|
+
path = read_exec_info
|
108
|
+
if path.to_s.strip.empty?
|
109
|
+
# If not present in ExecuteInfo, try use sensible default
|
110
|
+
path = BASE_DIR[platform]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
path = File.expand_path(path) if platform != PF_WINDOWS
|
114
|
+
|
115
|
+
ENV["SC2PATH"] = path
|
116
|
+
end
|
117
|
+
|
118
|
+
# Replays directory based on install_dir
|
119
|
+
# @return [String] replays directory
|
120
|
+
def replay_dir
|
121
|
+
Pathname(install_dir).join("Replays").to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
# Maps directory based on install_dir
|
125
|
+
# @return [String] maps directory
|
126
|
+
def maps_dir
|
127
|
+
# Use Maps if exists, or as fallback if alternative not found
|
128
|
+
dir = Pathname(install_dir).join("Maps")
|
129
|
+
return dir.to_s if dir.exist?
|
130
|
+
|
131
|
+
# Use lowercase "maps" only if it exists
|
132
|
+
dir_alt = Pathname(install_dir).join("maps")
|
133
|
+
dir = dir_alt if dir_alt.exist?
|
134
|
+
|
135
|
+
dir.to_s
|
136
|
+
end
|
137
|
+
|
138
|
+
# Working directory if required by platform
|
139
|
+
# Some platforms the correct working directory for launch to find linked libraries
|
140
|
+
# @return [String,nil] string path or nil if not required
|
141
|
+
def exec_working_dir
|
142
|
+
cwd = WORKING_DIR[platform]
|
143
|
+
return nil if cwd.nil?
|
144
|
+
|
145
|
+
Pathname(install_dir).join(cwd).to_s
|
146
|
+
end
|
147
|
+
|
148
|
+
# Gets a path to latest Sc2 executable, or specific build's executable if defined
|
149
|
+
# @param base_build [Integer, nil] version to use if installed
|
150
|
+
# @return [String, nil] path to executable
|
151
|
+
# noinspection RubyMismatchedReturnType
|
152
|
+
def executable(base_build: nil)
|
153
|
+
# Use base_build if supplied
|
154
|
+
unless base_build.nil?
|
155
|
+
path = Pathname.new(version_dir).join("Base#{base_build}")
|
156
|
+
return path.join(BIN_PATH[platform]).to_s if path.exist?
|
157
|
+
end
|
158
|
+
# Get highest build number for folders /^Base\d$/
|
159
|
+
pattern = /Base(\d+)$/
|
160
|
+
path = Pathname.new(version_dir).glob("Base*")
|
161
|
+
.max { |a, b| a.to_s.match(pattern)[1] <=> b.to_s.match(pattern)[1] }
|
162
|
+
return path.join(BIN_PATH[platform]).to_s if path&.exist?
|
163
|
+
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns project root directory
|
168
|
+
# @return [String] path because bundler does.
|
169
|
+
def project_root_dir
|
170
|
+
return Bundler.root.to_s if defined?(Bundler)
|
171
|
+
|
172
|
+
Dir.pwd
|
173
|
+
end
|
174
|
+
|
175
|
+
# For local and ladder play, your files modified at runtime should live in ./data
|
176
|
+
# @return [String] path to ./data
|
177
|
+
def bot_data_dir
|
178
|
+
dir = Pathname("./data")
|
179
|
+
dir.mkdir unless dir.exist?
|
180
|
+
dir.to_s
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the local replay folder
|
184
|
+
# For local play, your replay filse are saved to ./replays
|
185
|
+
# @return [String] path to ./replays
|
186
|
+
def bot_data_replay_dir
|
187
|
+
dir = Pathname("./replays")
|
188
|
+
dir.mkdir unless dir.exist?
|
189
|
+
dir.to_s
|
190
|
+
end
|
191
|
+
|
192
|
+
# @private
|
193
|
+
# Root directory of gem itself for other bundled files
|
194
|
+
# @return [Pathname] path to gem folder which contains lib/,data/,etc.
|
195
|
+
def gem_root
|
196
|
+
Pathname.new(__dir__.to_s).parent.parent.expand_path
|
197
|
+
end
|
198
|
+
|
199
|
+
# @private
|
200
|
+
# Directory of template folders
|
201
|
+
# @return [Pathname] path to where template directories live, i.e. "new" and "ladder"
|
202
|
+
def template_root
|
203
|
+
Pathname.new(__dir__.to_s).parent.join("templates").expand_path
|
204
|
+
end
|
205
|
+
|
206
|
+
# @private
|
207
|
+
# Path to shipped versions.json
|
208
|
+
# @return [Pathname] path
|
209
|
+
def gem_data_versions_path
|
210
|
+
Pathname.new(gem_root).join("data", "versions.json")
|
211
|
+
end
|
212
|
+
|
213
|
+
# Gets system temp directory and creates /sc2ai
|
214
|
+
# @return [String] temporary directory for use with -tempDir
|
215
|
+
def generate_temp_dir
|
216
|
+
temp_dir = Pathname(Dir.tmpdir).join("sc2ai").mkpath
|
217
|
+
temp_dir.to_s
|
218
|
+
end
|
219
|
+
|
220
|
+
# Checks if running WSL
|
221
|
+
# @return [Boolean]
|
222
|
+
def wsl?
|
223
|
+
Gem::Platform.local.os == "linux" && (ENV.fetch("IS_WSL", false) || ENV.fetch("WSL_DISTRO_NAME", false))
|
224
|
+
end
|
225
|
+
|
226
|
+
# Checks if running WSL2 specifically on top of wsl? being true
|
227
|
+
# @return [Boolean]
|
228
|
+
def wsl2?
|
229
|
+
wsl? && ENV.fetch("WSL_INTEROP", false)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Convert a path like "C:\\path\\To a\\Location" to "/mnt/c/path/To a/Location"
|
233
|
+
# @param path [String]
|
234
|
+
# @return [String] path converted
|
235
|
+
def win_to_wsl(path:)
|
236
|
+
"/mnt/" + path.sub(/\A([A-Z])(:)/) { ::Regexp.last_match(1).to_s.downcase }.tr("\\", "/")
|
237
|
+
end
|
238
|
+
|
239
|
+
# Convert a path like "/mnt/c/path/To a/Location" to "C:\\path\\To a\\Location"
|
240
|
+
# @param path [String]
|
241
|
+
# @return [String] path converted
|
242
|
+
def wsl_to_win(path:)
|
243
|
+
path.sub(%r{\A/mnt/([a-z])}) { "#{::Regexp.last_match(1).to_s.upcase}:" }.tr("/", "\\")
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
# Versions directory based on install_dir
|
249
|
+
# @return [String] install_dir + "/Versions"
|
250
|
+
def version_dir
|
251
|
+
Pathname(install_dir).join("Versions").to_s
|
252
|
+
end
|
253
|
+
|
254
|
+
# Loads version data
|
255
|
+
# @return [JSON] versions.json
|
256
|
+
def version_json
|
257
|
+
JSON.load_file(Pathname(__dir__.to_s).join("../data/versions.json"))
|
258
|
+
end
|
259
|
+
|
260
|
+
# Reads contents for ExecuteInfo.txt
|
261
|
+
# @return [String] contents of exec_info or empty string
|
262
|
+
def read_exec_info
|
263
|
+
return "" unless EXEC_INFO_PATH[platform]
|
264
|
+
|
265
|
+
# Read path ExecuteInfo.txt
|
266
|
+
exec_info_path = File.join(Dir.home, EXEC_INFO_PATH[platform])
|
267
|
+
content = File.read(exec_info_path)
|
268
|
+
dir = content.match(/ = (.*)Versions/)[1].to_s
|
269
|
+
|
270
|
+
# Platform-specific patches
|
271
|
+
if platform == PF_WSL1 || platform == PF_WSL2
|
272
|
+
dir = win_to_wsl(path: dir)
|
273
|
+
raise Pathname(dir).inspect
|
274
|
+
elsif PF_WINDOWS
|
275
|
+
dir = dir.tr("\\", "/")
|
276
|
+
end
|
277
|
+
|
278
|
+
dir = dir.chomp("\\").chomp("/")
|
279
|
+
return dir if File.exist?(dir)
|
280
|
+
rescue
|
281
|
+
""
|
282
|
+
else
|
283
|
+
""
|
284
|
+
end
|
285
|
+
|
286
|
+
# # Access singleton via Paths.instance
|
287
|
+
# private
|
288
|
+
#
|
289
|
+
# def instance
|
290
|
+
# @_instance ||= new(self.platform)
|
291
|
+
# end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|