sc2ai 0.0.0.pre → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/data.json +1 -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,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "async"
|
4
|
+
require "async/io/stream"
|
5
|
+
require "async/http/endpoint"
|
6
|
+
require "async/websocket"
|
7
|
+
require_relative "connection/requests"
|
8
|
+
|
9
|
+
module Sc2
|
10
|
+
# Manages client connection to the Api
|
11
|
+
class Connection
|
12
|
+
# Api requests
|
13
|
+
include Sc2::Connection::Requests
|
14
|
+
|
15
|
+
attr_accessor :host, :port, :websocket
|
16
|
+
|
17
|
+
# Last known game status, i.e. :launched, :ended, :unknown
|
18
|
+
# :launched // Game has been launch and is not yet doing anything.
|
19
|
+
# :init_game // Create game has been called, and the host is awaiting players.
|
20
|
+
# :in_game // In a single or multiplayer game.
|
21
|
+
# :in_replay // In a replay.
|
22
|
+
# :ended // Game has ended, can still request game info, but ready for a new game.
|
23
|
+
# :quit // Application is shutting down.
|
24
|
+
# :unknown // Should not happen, but indicates an error if it occurs.
|
25
|
+
# @return [Symbol] game status
|
26
|
+
attr_reader :status
|
27
|
+
|
28
|
+
# @!attribute listeners
|
29
|
+
# @return [Hash<String,Array>] of callbacks
|
30
|
+
attr_reader :listeners
|
31
|
+
|
32
|
+
# @param host [String]
|
33
|
+
# @param port [Integer]
|
34
|
+
# #param [Sc2::CallbackListener] listener
|
35
|
+
def initialize(host:, port:)
|
36
|
+
@host = host
|
37
|
+
@port = port
|
38
|
+
@listeners = {}
|
39
|
+
@websocket = nil
|
40
|
+
@status = :unknown
|
41
|
+
# Only allow one request at a time.
|
42
|
+
# TODO: Since it turns out the client websocket can only handle 1 request at a time, we don't stricly need Async
|
43
|
+
@scheduler = Async::Semaphore.new(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Attempts to connect for a period of time, ignoring errors nad performing on_* callbacks
|
47
|
+
# @return [void]
|
48
|
+
def connect
|
49
|
+
attempt = 1
|
50
|
+
Sc2.logger.debug { "Waiting for client..." } if (attempt % 5).zero?
|
51
|
+
|
52
|
+
begin
|
53
|
+
@websocket = Async::WebSocket::Client.connect(endpoint) # , handler: Sc2::Connection::Connection)
|
54
|
+
@listeners[ConnectionListener.name]&.each { _1.on_connected(self) }
|
55
|
+
# do initial ping to ensure status is set and connection is working
|
56
|
+
response_ping = ping
|
57
|
+
Sc2.logger.debug { "Game version: #{response_ping.game_version}" }
|
58
|
+
rescue Errno::ECONNREFUSED
|
59
|
+
raise Error, "Connection timeout. Max retry exceeded." unless (attempt += 1) < 30 # 30s attempts
|
60
|
+
|
61
|
+
@listeners[ConnectionListener.name]&.each { _1.on_connection_waiting(self) }
|
62
|
+
sleep(1)
|
63
|
+
retry
|
64
|
+
rescue Error => e
|
65
|
+
Sc2.logger.error "#{e.class}: #{e.message}"
|
66
|
+
@listeners[ConnectionListener.name]&.each { _1.on_disconnect(self) }
|
67
|
+
end
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Closes Connection to client
|
72
|
+
# @return [void]
|
73
|
+
def close
|
74
|
+
@websocket&.close
|
75
|
+
@listeners[ConnectionListener.name]&.each { _1.on_disconnect(self) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Add a listener of specific callback type
|
79
|
+
# @param listener [Object]
|
80
|
+
# @param klass [Module<Sc2::Connection::ConnectionListener>,Module<Sc2::Connection::StatusListener>]
|
81
|
+
def add_listener(listener, klass:)
|
82
|
+
@listeners[klass.to_s] ||= []
|
83
|
+
@listeners[klass.to_s].push(listener)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Removes a listener of specific callback type
|
87
|
+
# @param listener [Object]
|
88
|
+
# @param klass [Module<Sc2::Connection::ConnectionListener>,Module<Sc2::Connection::StatusListener>]
|
89
|
+
def remove_listener(listener, klass:)
|
90
|
+
@listeners[klass.to_s].delete(listener)
|
91
|
+
end
|
92
|
+
|
93
|
+
# ---------------------------------------------------------
|
94
|
+
# Sends a request synchronously and returns Api::Response type
|
95
|
+
# @return [Api::Response] response
|
96
|
+
def send_request(request)
|
97
|
+
@scheduler.async do |_task|
|
98
|
+
# r = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) #debug
|
99
|
+
# name = request.is_a?(String) ? request : request.request #debug
|
100
|
+
request = request.to_proto unless request.is_a?(String)
|
101
|
+
@websocket.send_binary(request)
|
102
|
+
response = Api::Response.decode(@websocket.read.to_str)
|
103
|
+
|
104
|
+
if @status != response.status
|
105
|
+
@status = response.status
|
106
|
+
@listeners[StatusListener.name]&.each { _1.on_status_change(@status) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Sc2.logger.debug { response }
|
110
|
+
# puts "#{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000} - #{name}" #debug
|
111
|
+
response
|
112
|
+
end.wait
|
113
|
+
rescue EOFError => e
|
114
|
+
Sc2.logger.error e
|
115
|
+
close
|
116
|
+
end
|
117
|
+
|
118
|
+
# Sends and ignores response.
|
119
|
+
# Meant to be used as optimization for RequestStep.
|
120
|
+
# No other command sends and ignores.
|
121
|
+
# Expects request to be to_proto'd already
|
122
|
+
# @return [void]
|
123
|
+
def send_request_and_ignore(request)
|
124
|
+
@scheduler.async do |_task|
|
125
|
+
@websocket.send_binary(request)
|
126
|
+
while @websocket.read_frame
|
127
|
+
if @websocket.frames.last&.finished?
|
128
|
+
@websocket.frames = []
|
129
|
+
break
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end.wait
|
133
|
+
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
attr_writer :status, :listeners
|
140
|
+
|
141
|
+
# @return [HTTP::Endpoint] websocket url for establishing protobuf connection
|
142
|
+
def endpoint
|
143
|
+
Async::HTTP::Endpoint.parse("ws://#{@host}:#{@port}/sc2api")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Client
|
5
|
+
# Attributes shared by Configuration and Client
|
6
|
+
module ConfigurableOptions
|
7
|
+
# @!attribute host
|
8
|
+
# Sc2 host param on which to listen for connections, default '0.0.0.0'
|
9
|
+
#
|
10
|
+
# Launch param: -host 0.0.0.0
|
11
|
+
# @return [String,nil]
|
12
|
+
attr_accessor :host
|
13
|
+
|
14
|
+
# @!attribute data_dir
|
15
|
+
# Override the path to find the data package.
|
16
|
+
#
|
17
|
+
# Required if the binary is not in the standard versions folder location.
|
18
|
+
#
|
19
|
+
# Launch param: -dataDir ../../
|
20
|
+
# @return [String,nil]
|
21
|
+
attr_accessor :data_dir
|
22
|
+
|
23
|
+
# @!attribute verbose
|
24
|
+
# If set to true, will send param to client.
|
25
|
+
#
|
26
|
+
# Enables logging of all protocol requests/responses to std::err.
|
27
|
+
#
|
28
|
+
# Launch param: -verbose
|
29
|
+
# @return [Boolean]
|
30
|
+
attr_accessor :verbose
|
31
|
+
|
32
|
+
# @!attribute temp_dir
|
33
|
+
# Override the path if you really need to set your own temp dir
|
34
|
+
#
|
35
|
+
# Implicit default is /tmp/
|
36
|
+
#
|
37
|
+
# Launch param: -tempDir ../../
|
38
|
+
# @return [String,nil]
|
39
|
+
attr_accessor :temp_dir
|
40
|
+
|
41
|
+
# @!attribute egl_path
|
42
|
+
# Sets the path the to hardware rendering library.
|
43
|
+
#
|
44
|
+
# Required for using the rendered interface with hardware rendering
|
45
|
+
#
|
46
|
+
# Launch param: -eglpath
|
47
|
+
#
|
48
|
+
# Example: /usr/lib/nvidia-384/libEGL.so
|
49
|
+
# @return [String,nil]
|
50
|
+
attr_accessor :egl_path
|
51
|
+
|
52
|
+
# @!attribute osmesa_path
|
53
|
+
# Sets the path the to software rendering library.
|
54
|
+
#
|
55
|
+
# Required for using the rendered interface with software rendering
|
56
|
+
#
|
57
|
+
# Launch param: -eglpath
|
58
|
+
#
|
59
|
+
# Example: /usr/lib/x86_64-linux-gnu/libOSMesa.so
|
60
|
+
# @return [String,nil]
|
61
|
+
attr_accessor :osmesa_path
|
62
|
+
|
63
|
+
# @!attribute display_mode
|
64
|
+
# Launch param: -displayMode
|
65
|
+
# @return [0,1,nil] 0 for window, 1 for fullscreen, nil for system default
|
66
|
+
attr_accessor :display_mode
|
67
|
+
# @!attribute windowwidth
|
68
|
+
# pixel width of game window
|
69
|
+
# @return [Integer]
|
70
|
+
attr_accessor :windowwidth # -windowwidth
|
71
|
+
# @!attribute windowheight
|
72
|
+
# pixel height of game window
|
73
|
+
# @return [Integer]
|
74
|
+
attr_accessor :windowheight # -windowheight
|
75
|
+
# @!attribute windowx
|
76
|
+
# left position of window if windowed
|
77
|
+
# @return [Integer]
|
78
|
+
attr_accessor :windowx # -windowx
|
79
|
+
# @!attribute windowy
|
80
|
+
# top position of window if windowed
|
81
|
+
# @return [Integer]
|
82
|
+
attr_accessor :windowy # -windowy
|
83
|
+
|
84
|
+
# @!attribute version
|
85
|
+
# Version number such as "4.10". Leave blank to use latest
|
86
|
+
# @return [String,nil]
|
87
|
+
attr_accessor :version
|
88
|
+
|
89
|
+
# Resets configurable launch options to their defaults
|
90
|
+
def load_default_launch_options
|
91
|
+
@host = "0.0.0.0" # -listen
|
92
|
+
if Paths.wsl?
|
93
|
+
@host = "#{Socket.gethostname}.mshome.net"
|
94
|
+
elsif Gem.win_platform?
|
95
|
+
@host = "127.0.0.1"
|
96
|
+
end
|
97
|
+
|
98
|
+
@display_mode = 0 # -displayMode
|
99
|
+
@windowwidth = nil # -windowwidth
|
100
|
+
@windowheight = nil # -windowheight
|
101
|
+
@windowx = nil # -windowx
|
102
|
+
@windowy = nil # -windowy
|
103
|
+
@verbose = false # -verbose
|
104
|
+
|
105
|
+
# Linux
|
106
|
+
@data_dir = nil # -dataDir '../../'
|
107
|
+
@temp_dir = nil # -tempDir '/tmp/'
|
108
|
+
@egl_path = nil # -eglpath '/usr/lib/nvidia-384/libEGL.so'
|
109
|
+
@osmesa_path = nil # -osmesapath '/usr/lib/x86_64-linux-gnu/libOSMesa.so'
|
110
|
+
|
111
|
+
@version = nil # calculates -dataVersion and Base% executable
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "client/configurable_options"
|
4
|
+
|
5
|
+
module Sc2
|
6
|
+
# Manages client connection to the Api
|
7
|
+
class Client
|
8
|
+
include Sc2::Client::ConfigurableOptions
|
9
|
+
|
10
|
+
# @!attribute port
|
11
|
+
# Sc2 port param on which to listen for connections, default is randomly selected<br>
|
12
|
+
# Launch param: -port 12345
|
13
|
+
# @return [Integer]
|
14
|
+
attr_accessor :port
|
15
|
+
|
16
|
+
# @!attribute base_build
|
17
|
+
# Sc2 build number determines where to look for correct executable version binary
|
18
|
+
# @return [Integer]
|
19
|
+
attr_accessor :base_build
|
20
|
+
|
21
|
+
# @!attribute data_version
|
22
|
+
# Sc2 data param, typically only required when launching older versions and Linux
|
23
|
+
# Launch param: -dataVersion "B89B5D6FA7CBF6452E721311BFBC6CB2"
|
24
|
+
# @return [String]
|
25
|
+
attr_accessor :data_version
|
26
|
+
|
27
|
+
# Whether the Sc2 process is running or not
|
28
|
+
# @return [Boolean]
|
29
|
+
def running?
|
30
|
+
!!@task&.running?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Initialize new Sc2 client (starts with #launch)
|
34
|
+
# @param host [String] to listen on, i.e. "0.0.0.0", "127.0.0.1"
|
35
|
+
# @param port [Integer] 5001
|
36
|
+
# @param options [Hash] (see Sc2::Client::ConfigurableOptions)
|
37
|
+
def initialize(host:, port:, **options)
|
38
|
+
raise Error, "Invalid port: #{port}" if port.to_s.empty?
|
39
|
+
|
40
|
+
load_default_launch_options
|
41
|
+
options.each do |key, value|
|
42
|
+
instance_variable_set(:"@#{key}", value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Require these at a minimum
|
46
|
+
@port = port
|
47
|
+
@host = host
|
48
|
+
return if @version.nil?
|
49
|
+
|
50
|
+
use_version(@version.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Launches and returns pid or proc
|
54
|
+
def launch
|
55
|
+
cwd = Paths.exec_working_dir
|
56
|
+
@task = Async do |task|
|
57
|
+
options = {}
|
58
|
+
|
59
|
+
if Gem.win_platform?
|
60
|
+
options[:new_pgroup] = true
|
61
|
+
else
|
62
|
+
options[:pgroup] = true
|
63
|
+
end
|
64
|
+
unless cwd.nil?
|
65
|
+
options[:chdir] = cwd
|
66
|
+
end
|
67
|
+
begin
|
68
|
+
::Async::Process.spawn(command_string, **options)
|
69
|
+
rescue
|
70
|
+
Sc2.logger.info("Client exited unexpectedly")
|
71
|
+
task&.stop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Stops the Sc2 instance<br/>
|
77
|
+
# This naturally disconnects attached players too
|
78
|
+
# @return [void]
|
79
|
+
def stop
|
80
|
+
@task&.stop
|
81
|
+
end
|
82
|
+
|
83
|
+
# Reads "base-version" and "data-hash" for corresponding version
|
84
|
+
# @example
|
85
|
+
# client.base_build => nil
|
86
|
+
# client.data_version => nil
|
87
|
+
# client.use_version("4.10")
|
88
|
+
# client.base_build => 75689
|
89
|
+
# client.data_version => "B89B5D6FA7CBF6452E721311BFBC6CB2"
|
90
|
+
# @param version [String]
|
91
|
+
# return [Array<Integer,String>] tuple base_build and data_version
|
92
|
+
def use_version(version)
|
93
|
+
found_base_build = nil
|
94
|
+
found_data_version = nil
|
95
|
+
versions_json.each do |node|
|
96
|
+
version_clean = version.gsub(".#{node["base-version"]}", "")
|
97
|
+
if version_clean == node["label"]
|
98
|
+
found_base_build = node["base-version"]
|
99
|
+
found_data_version = node["data-hash"]
|
100
|
+
@version = version_clean
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if found_base_build.nil? || found_data_version.nil?
|
105
|
+
Sc2.logger.warn "Requested version #{version} not found. Omit to auto-discover latest installed version"
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
@base_build = found_base_build
|
110
|
+
@data_version = found_data_version
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# @!attribute task
|
117
|
+
# The async task running Sc2. Used when interrupting.
|
118
|
+
# @return [Async::Task]
|
119
|
+
attr_accessor :task
|
120
|
+
|
121
|
+
# @private
|
122
|
+
# Takes all configuration and Sc2 executable string with arguments
|
123
|
+
# @return [String] command to launch Sc2
|
124
|
+
def command_string
|
125
|
+
command = "\"#{Sc2::Paths.executable(base_build: @base_build)}\" "
|
126
|
+
command += " -port #{@port}"
|
127
|
+
if Paths.platform == Paths::PF_WSL2
|
128
|
+
# For WSL2, always let windows listen on all 0.0.0.0
|
129
|
+
# and let the ws connect @host which is the internal bridged windows ip
|
130
|
+
command += " -listen 0.0.0.0"
|
131
|
+
else
|
132
|
+
command += " -listen #{@host}" unless @host.nil?
|
133
|
+
end
|
134
|
+
|
135
|
+
command += " -dataVersion #{@data_version}" unless @data_version.nil?
|
136
|
+
command += " -verbose" if @verbose
|
137
|
+
|
138
|
+
command += " -displayMode #{@display_mode}" unless @display_mode.nil?
|
139
|
+
command += " -windowwidth #{@windowwidth}" unless @windowwidth.nil?
|
140
|
+
command += " -windowheight #{@windowheight}" unless @windowheight.nil?
|
141
|
+
command += " -windowx #{@windowx}" unless @windowx.nil?
|
142
|
+
command += " -windowy #{@windowy}" unless @windowy.nil?
|
143
|
+
|
144
|
+
command += " -dataDir #{@data_dir}" unless @data_dir.nil?
|
145
|
+
command += " -tempDir #{@temp_dir}" unless @temp_dir.nil?
|
146
|
+
command += " -eglpath #{@egl_path}" unless @egl_path.nil?
|
147
|
+
command += " -osmesapath #{@osmesa_path}" unless @osmesa_path.nil?
|
148
|
+
|
149
|
+
command
|
150
|
+
end
|
151
|
+
|
152
|
+
# @private
|
153
|
+
# Reads bundled versions.json
|
154
|
+
# @return [JSON] contents of versions.json
|
155
|
+
def versions_json
|
156
|
+
JSON.load_file(Paths.gem_data_versions_path)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Sc2
|
7
|
+
# Starts, stops and holds reference to clients
|
8
|
+
class ClientManager
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
class << self
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :instance, :obtain, :get, :start, :stop
|
14
|
+
end
|
15
|
+
|
16
|
+
# Gets client for player X or starts an instance
|
17
|
+
def obtain(player_index)
|
18
|
+
client = get(player_index)
|
19
|
+
if client.nil? || !client.running?
|
20
|
+
client = start(player_index)
|
21
|
+
@clients[player_index] = client
|
22
|
+
end
|
23
|
+
client
|
24
|
+
end
|
25
|
+
|
26
|
+
# Gets Sc2::Client client for player index
|
27
|
+
# @param player_index [Integer] normally 0,1
|
28
|
+
# @return [Sc2::Connection, nil] running client or nil if not set
|
29
|
+
def get(player_index)
|
30
|
+
@clients[player_index]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Starts an Sc2 client for player_index. Will stop existing client if present.
|
34
|
+
# @param player_index [Integer] normally 0,1
|
35
|
+
# @return [Sc2::Client] started client
|
36
|
+
def start(player_index)
|
37
|
+
existing = @clients[player_index]
|
38
|
+
stop(player_index) if !existing.nil? && existing.running?
|
39
|
+
|
40
|
+
client = Client.new(host: @host, port: @ports[player_index], **Sc2.config.to_h)
|
41
|
+
client.launch
|
42
|
+
@clients[player_index] = client
|
43
|
+
client
|
44
|
+
end
|
45
|
+
|
46
|
+
# Stops client at player index
|
47
|
+
# @param player_index [Integer]
|
48
|
+
# @return [void]
|
49
|
+
def stop(player_index)
|
50
|
+
return unless @clients[player_index]
|
51
|
+
|
52
|
+
@clients[player_index]&.stop
|
53
|
+
@clients[player_index] = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_accessor :clients, :ports
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
super
|
62
|
+
@ports = Sc2.config.ports
|
63
|
+
@ports = [] unless ports.is_a? Array
|
64
|
+
@ports.push(Ports.random_available_port) while @ports.size < 3
|
65
|
+
|
66
|
+
@host = Sc2.config.host
|
67
|
+
@clients = []
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
# Helps easily locate a map and fetch input for Api::LocalMap
|
5
|
+
class MapFile
|
6
|
+
# File extension used for maps
|
7
|
+
EXTENSION = ".SC2Map"
|
8
|
+
|
9
|
+
# @!attribute [r] path
|
10
|
+
# @return [String] path map location for use in LocalMap.map_path
|
11
|
+
attr_reader :path
|
12
|
+
|
13
|
+
# Accepts a map file name and initializes a local map object
|
14
|
+
# @example
|
15
|
+
# map = Sc2::Map.new("2000AtmospheresAIE")
|
16
|
+
# map = Sc2::Map.new("2000AtmospheresAIE.SC2Map")
|
17
|
+
# map = Sc2::Map.new("/absolute/path/to/2000AtmospheresAIE.SC2Map")
|
18
|
+
# # If within your Sc2 Maps folder, you have a sub-folder "sc2ai_2022_season3"
|
19
|
+
# map = Sc2::Map.new("sc2ai_2022_season3/2000AtmospheresAIE.SC2Map")
|
20
|
+
# @param name [String] absolute path or path relative to maps
|
21
|
+
def initialize(name)
|
22
|
+
raise Error if name.empty?
|
23
|
+
|
24
|
+
name = "#{name}#{EXTENSION}" unless name.end_with? EXTENSION
|
25
|
+
|
26
|
+
@path = name.to_s
|
27
|
+
return if Pathname(name).absolute?
|
28
|
+
|
29
|
+
@path = Pathname(Paths.maps_dir).glob("**/#{name}").first.to_s
|
30
|
+
if Paths.wsl?
|
31
|
+
@path = Paths.wsl_to_win(path: @path)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns contents of map file for user with LocalMap.map_data
|
36
|
+
# @return [String, nil] contents of file
|
37
|
+
def data
|
38
|
+
file = Pathname(@path)
|
39
|
+
return file.read if file.exist?
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_writer :path
|
47
|
+
end
|
48
|
+
end
|