sc2ai 0.0.0.pre → 0.0.3
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 +22946 -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 +37900 -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 +1951 -0
- data/lib/sc2ai/api/buff_id.rb +316 -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 +82 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2342 -0
- data/lib/sc2ai/api/unit_type_id.rb +2074 -0
- data/lib/sc2ai/api/upgrade_id.rb +312 -0
- data/lib/sc2ai/cli/cli.rb +177 -0
- data/lib/sc2ai/cli/ladderzip.rb +173 -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
- metadata +353 -9
@@ -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
|