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,88 @@
|
|
1
|
+
require "sc2ai/paths"
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
# Command line utilities
|
5
|
+
class Cli < Thor
|
6
|
+
class New < Thor::Group
|
7
|
+
include Thor::Actions
|
8
|
+
desc "Creates a new bot"
|
9
|
+
|
10
|
+
# Define arguments and options
|
11
|
+
argument :botname, required: true, desc: "Bot name as on aiarena. Used as a file name (short, alpha-num, no spaces!)"
|
12
|
+
argument :race, required: true, desc: "Choose a race", enum: %w[Terran Zerg Protoss Random]
|
13
|
+
|
14
|
+
def self.source_root
|
15
|
+
Sc2::Paths.template_root.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def checkname
|
19
|
+
race_arg = Sc2::Cli::New.arguments.find { |a| a.name == "race" }
|
20
|
+
unless race_arg.enum.include?(race)
|
21
|
+
raise Thor::MalformattedArgumentError, "Invalid race #{race}, must be one of #{race_arg.enum}"
|
22
|
+
end
|
23
|
+
|
24
|
+
say "We need to create a filename and classname from botname '#{@botname}'"
|
25
|
+
say "You rename classes and organize files in any way, as long as you generate a valid $bot in boot.rb"
|
26
|
+
|
27
|
+
@botname = botname.gsub(/[^0-9a-z]/i, "")
|
28
|
+
@directory = @botname.downcase
|
29
|
+
@classname = botname.split(/[^0-9a-z]/i).collect { |s| s.sub(/^./, &:upcase) }.join
|
30
|
+
@bot_file = @classname.split(/(?=[A-Z])/).join("_").downcase.concat(".rb")
|
31
|
+
say "Race: #{race}"
|
32
|
+
say "Class name: #{@classname}"
|
33
|
+
say "Create directory: ./#{@directory}"
|
34
|
+
say "Bot file: ./#{@directory}/#{@bot_file}"
|
35
|
+
|
36
|
+
unless ask("Does this look ok?", limited_to: ["y", "n"], default: "y") == "y"
|
37
|
+
raise SystemExit
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_target
|
42
|
+
if Pathname("./#{@directory}").exist?
|
43
|
+
say "Folder already exists. Refusing to overwrite.", :red
|
44
|
+
raise SystemExit
|
45
|
+
end
|
46
|
+
|
47
|
+
empty_directory "./#{@directory}"
|
48
|
+
self.destination_root = Pathname("./#{@directory}").to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_boot
|
52
|
+
template("new/boot.rb", "boot.rb")
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_example_match
|
56
|
+
template("new/run_example_match.rb", "run_example_match.rb")
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_gemfile
|
60
|
+
template("new/Gemfile", "Gemfile")
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_botfile
|
64
|
+
template("new/my_bot.rb", @bot_file)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_ignorefile
|
68
|
+
template("new/.ladderignore", ".ladderignore")
|
69
|
+
end
|
70
|
+
|
71
|
+
def copy_api
|
72
|
+
directory("new/api", "api")
|
73
|
+
end
|
74
|
+
|
75
|
+
def bye
|
76
|
+
say ""
|
77
|
+
say "Bot generated, next steps:"
|
78
|
+
say ""
|
79
|
+
say "cd #{@directory} && bundle install", :green
|
80
|
+
say ""
|
81
|
+
say "Once your project is ready, if you haven't done so, setup SC2 v4.10 with:"
|
82
|
+
say ""
|
83
|
+
say "bundle exec sc2ai setup410", :green
|
84
|
+
say ""
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "pathname"
|
5
|
+
require_relative "local_play/client/configurable_options"
|
6
|
+
|
7
|
+
module Sc2
|
8
|
+
# Global config manager for runtime
|
9
|
+
# @example Manual configuration block
|
10
|
+
# Sc2.config do |config|
|
11
|
+
# config.sc2_platform = "WineLinux"
|
12
|
+
# config.sc2_path = "/c/Program Files (x86)/StarCraft II/"
|
13
|
+
# config.ports = [5001,5002,5003]
|
14
|
+
# config.host = '127.0.0.1'
|
15
|
+
# end
|
16
|
+
class Configuration
|
17
|
+
# Include client launch options
|
18
|
+
include Sc2::Client::ConfigurableOptions
|
19
|
+
|
20
|
+
# Attributes permitted to be read and save from config yaml
|
21
|
+
CONFIG_ATTRIBUTES = %i[
|
22
|
+
sc2_platform
|
23
|
+
sc2_path
|
24
|
+
version
|
25
|
+
ports
|
26
|
+
host
|
27
|
+
display_mode
|
28
|
+
windowwidth
|
29
|
+
windowheight
|
30
|
+
windowx
|
31
|
+
windowy
|
32
|
+
verbose
|
33
|
+
data_dir
|
34
|
+
temp_dir
|
35
|
+
egl_path
|
36
|
+
osmesa_path
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
# @!attribute sc2_platform
|
40
|
+
# Sc2 platform config alias will override ENV["SC2PF"]
|
41
|
+
# @return [String] (see Sc2::Paths#platform)
|
42
|
+
attr_accessor :sc2_platform
|
43
|
+
|
44
|
+
# @!attribute sc2_path
|
45
|
+
# Sc2 Path config alias will override ENV["SC2PATH"]
|
46
|
+
# @return [String] sc2_path (see Sc2::Paths#platform)
|
47
|
+
attr_accessor :sc2_path
|
48
|
+
|
49
|
+
# @!attribute ports
|
50
|
+
# if empty, a random port will be picked when launching
|
51
|
+
# Launch param: -listen
|
52
|
+
# @return [Array<Integer>]
|
53
|
+
attr_accessor :ports
|
54
|
+
|
55
|
+
# Create a new Configuration and sets defaults and loads config from yaml
|
56
|
+
# @return [Sc2::Configuration]
|
57
|
+
def initialize
|
58
|
+
set_defaults
|
59
|
+
|
60
|
+
load_config(config_file) if config_file.exist?
|
61
|
+
|
62
|
+
# create temp dir on linux
|
63
|
+
ensure_temp_dir
|
64
|
+
end
|
65
|
+
|
66
|
+
# Config file location
|
67
|
+
# @return [Pathname] path
|
68
|
+
def config_file
|
69
|
+
# Pathname(Paths.project_root_dir).join("config", "sc2ai.yml")
|
70
|
+
Pathname(Paths.project_root_dir).join("sc2ai.yml")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Sets defaults when initializing
|
74
|
+
# @return [void]
|
75
|
+
def set_defaults
|
76
|
+
@sc2_platform = Paths.platform
|
77
|
+
@sc2_path = Paths.install_dir
|
78
|
+
@ports = []
|
79
|
+
|
80
|
+
load_default_launch_options
|
81
|
+
end
|
82
|
+
|
83
|
+
# Writes this instance's attributes to yaml config_file
|
84
|
+
# @return [void]
|
85
|
+
def save_config
|
86
|
+
# config_file.dirname.mkpath unless config_file.dirname.exist?
|
87
|
+
config_file.write(to_yaml.to_s)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# Converts attributes to yaml
|
92
|
+
# @return [Hash] yaml matching stringified keys from CONFIG_ATTRIBUTES
|
93
|
+
def to_yaml
|
94
|
+
to_h.to_yaml
|
95
|
+
end
|
96
|
+
|
97
|
+
# Converts attributes to hash
|
98
|
+
# @return [Hash] hash matching stringified keys from CONFIG_ATTRIBUTES
|
99
|
+
def to_h
|
100
|
+
CONFIG_ATTRIBUTES.map do |name|
|
101
|
+
[name.to_s, instance_variable_get(:"@#{name}")]
|
102
|
+
end.to_h
|
103
|
+
end
|
104
|
+
|
105
|
+
# Loads YAML config
|
106
|
+
# @param file [Pathname,String] location of config file
|
107
|
+
# @return [Boolean] success/failure
|
108
|
+
def load_config(file)
|
109
|
+
file = Pathname(file) unless file.is_a? Pathname
|
110
|
+
return false if !file.exist? || file.size.nil?
|
111
|
+
|
112
|
+
begin
|
113
|
+
content = ::Psych.safe_load(file.read)
|
114
|
+
unless content.is_a? Hash
|
115
|
+
Sc2.logger.warn "Failed to load #{file} because it doesn't contain valid YAML hash"
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
CONFIG_ATTRIBUTES.map(&:to_s).each do |attribute|
|
119
|
+
next unless content.key?(attribute.to_s)
|
120
|
+
|
121
|
+
instance_variable_set(:"@#{attribute}", content[attribute])
|
122
|
+
end
|
123
|
+
return true
|
124
|
+
rescue ArgumentError, Psych::SyntaxError => e
|
125
|
+
Sc2.logger.warn "Failed to load #{file}, #{e}"
|
126
|
+
rescue Errno::EACCES
|
127
|
+
Sc2.logger.warn "Failed to load #{file} due to permissions problem."
|
128
|
+
end
|
129
|
+
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# Makes sure we have a temporary directory on linux if not specified
|
136
|
+
# @return [void]
|
137
|
+
def ensure_temp_dir
|
138
|
+
return unless Paths.platform == Paths::PF_LINUX
|
139
|
+
|
140
|
+
return unless @temp_dir.to_s.empty?
|
141
|
+
|
142
|
+
@temp_dir = Paths.generate_temp_dir
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Connection
|
5
|
+
# Callbacks should be included on your listening class
|
6
|
+
# noinspection RubyUnusedLocalVariable
|
7
|
+
module ConnectionListener
|
8
|
+
# Called when connection established to application
|
9
|
+
# @param connection [Sc2Ai::Connection]
|
10
|
+
# noinspection
|
11
|
+
def on_connected(connection)
|
12
|
+
Sc2.logger.debug { "#{self.class}.#{__method__} #{connection}" }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Called while waiting on connection to application
|
16
|
+
# @param connection [Sc2Ai::Connection]
|
17
|
+
# noinspection Lint/UnusedMethodArgument
|
18
|
+
def on_connection_waiting(connection)
|
19
|
+
Sc2.logger.debug { "#{self.class}.#{__method__} #{connection}" }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Called when disconnected from application
|
23
|
+
# @param connection [Sc2Ai::Connection]
|
24
|
+
# noinspection Lint/UnusedMethodArgument
|
25
|
+
def on_disconnect(connection)
|
26
|
+
Sc2.logger.debug { "#{self.class}.#{__method__} #{connection}" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,417 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Connection
|
5
|
+
# Sends protobuf requests over Connection to Client
|
6
|
+
module Requests
|
7
|
+
# GAME MANAGEMENT ----
|
8
|
+
|
9
|
+
# Send to host to initialize game
|
10
|
+
def create_game(map:, players:, realtime: false)
|
11
|
+
send_request_for create_game: Api::RequestCreateGame.new(
|
12
|
+
local_map: Api::LocalMap.new(map_path: map.path),
|
13
|
+
player_setup: players.map do |player|
|
14
|
+
Api::PlayerSetup.new(
|
15
|
+
type: player.type,
|
16
|
+
race: player.race,
|
17
|
+
player_name: player.name,
|
18
|
+
difficulty: player.difficulty,
|
19
|
+
ai_build: player.ai_build
|
20
|
+
)
|
21
|
+
end,
|
22
|
+
realtime:
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Send to host and all clients for game to begin.
|
27
|
+
# @param race [Google::Protobuf::EnumValue] Api::Race
|
28
|
+
# @param name [String] player name
|
29
|
+
# @param server_host [String] hostname or ip of sc2 client
|
30
|
+
# @param port_config [Sc2::PortConfig] port config auto or basic, using start port
|
31
|
+
# @param enable_feature_layer [Boolean] Enables the feature layer at 1x1 pixels
|
32
|
+
# @param interface_options [Hash]
|
33
|
+
# @option interface_options [Boolean] :raw (true) raw interface enabled, default true
|
34
|
+
# @option interface_options [Boolean] :score (false) score game info
|
35
|
+
# @option interface_options [Boolean] :show_cloaked (true) hows details about cloaked units
|
36
|
+
# @option interface_options [Boolean] :show_burrowed_shadows (true) shows some details for those that produce a shadow
|
37
|
+
# @option interface_options [Boolean] :show_placeholders (true) return placeholder units (buildings to be constructed)
|
38
|
+
# @option interface_options [Boolean] :raw_affects_selection (false) for live raw does whatever it wants, for local it keeps your selection by default.
|
39
|
+
# @option interface_options [Boolean] :raw_crop_to_playable_area (true) trims away unplayable parts of map, else map is 255x255 with dead space. performant if true.
|
40
|
+
def join_game(race:, name:, server_host:, port_config:, enable_feature_layer: false, interface_options: {})
|
41
|
+
interface_options ||= {}
|
42
|
+
|
43
|
+
send_request_for join_game: Api::RequestJoinGame.new(
|
44
|
+
# TODO: For Observer support, get player_index for observer,
|
45
|
+
# don't set race and pass observed_player_id: player_index
|
46
|
+
# observed_player_id: 0, # For observer
|
47
|
+
# --
|
48
|
+
race:,
|
49
|
+
player_name: name,
|
50
|
+
host_ip: server_host,
|
51
|
+
server_ports: port_config.server_port_set,
|
52
|
+
client_ports: port_config.client_port_sets,
|
53
|
+
options: Api::InterfaceOptions.new(
|
54
|
+
{
|
55
|
+
raw: true,
|
56
|
+
score: false,
|
57
|
+
feature_layer: feature_layer_interface_options(enable_feature_layer),
|
58
|
+
show_cloaked: true,
|
59
|
+
show_burrowed_shadows: true,
|
60
|
+
show_placeholders: true,
|
61
|
+
raw_affects_selection: Sc2.ladder?,
|
62
|
+
raw_crop_to_playable_area: true
|
63
|
+
}.merge!(interface_options)
|
64
|
+
)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @private
|
69
|
+
# Default options for feature layer, which enables it,
|
70
|
+
# but sets the map/minimap size to 1x1 for peak performance.
|
71
|
+
# A user can manually pass in it's own interface options
|
72
|
+
def feature_layer_interface_options(enabled)
|
73
|
+
return nil unless enabled
|
74
|
+
|
75
|
+
::Api::SpatialCameraSetup.new(
|
76
|
+
width: 1.0,
|
77
|
+
resolution: Api::Size2DI.new(x: 1, y: 1),
|
78
|
+
minimap_resolution: Api::Size2DI.new(x: 1, y: 1),
|
79
|
+
# width: 10.0,
|
80
|
+
# resolution: Api::Size2DI.new(x: 128, y: 128),
|
81
|
+
# minimap_resolution: Api::Size2DI.new(x: 16, y: 16),
|
82
|
+
crop_to_playable_area: true, # has no effect. minimap x and y are respected no matter what
|
83
|
+
allow_cheating_layers: false
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
protected :feature_layer_interface_options
|
88
|
+
|
89
|
+
# Single player only. Reinitializes the game with the same player setup.
|
90
|
+
def restart_game
|
91
|
+
send_request_for restart_game: Api::RequestRestartGame.new
|
92
|
+
end
|
93
|
+
|
94
|
+
# Given a replay file path or replay file contents, will start the replay
|
95
|
+
# @example
|
96
|
+
# Sc2.config do |config|
|
97
|
+
# config.version = "4.10"
|
98
|
+
# end
|
99
|
+
# Async do
|
100
|
+
# client = Sc2::ClientManager.obtain(0)
|
101
|
+
# observer = Sc2::Player::Observer.new
|
102
|
+
# observer.connect(host: client.host, port: client.port)
|
103
|
+
# pp observer.api.start_replay(
|
104
|
+
# replay_path: Pathname("./replays/test.SC2Replay").realpath
|
105
|
+
# )
|
106
|
+
# while observer.status == :in_replay
|
107
|
+
# # Step forward
|
108
|
+
# observer.api.step(1)
|
109
|
+
# # fresh observation info
|
110
|
+
# observation = observer.api.observation
|
111
|
+
# # fresh game info
|
112
|
+
# game_info = observer.api.game_info
|
113
|
+
# end
|
114
|
+
# ensure
|
115
|
+
# Sc2::ClientManager.stop(0)
|
116
|
+
# end
|
117
|
+
# @param replay_path [String] path to replay
|
118
|
+
# @param replay_data [String] alternative to file, binary string of replay_file.read
|
119
|
+
# @param map_data [String] optional binary string of SC2 map if not present in paths
|
120
|
+
# @param options [Hash] Api:RequestStartReplay options, such as disable_fog, observed_player_id, map_data
|
121
|
+
# @param [Hash] interface_options
|
122
|
+
def start_replay(replay_path: nil, replay_data: nil, map_data: nil, record_replay: true, interface_options: {}, **options)
|
123
|
+
raise Sc2::Error, "Missing replay." if replay_data.nil? && replay_path.nil?
|
124
|
+
|
125
|
+
interface_options ||= {}
|
126
|
+
send_request_for start_replay: Api::RequestStartReplay.new(
|
127
|
+
{
|
128
|
+
replay_path: replay_path.to_s,
|
129
|
+
replay_data: replay_data,
|
130
|
+
map_data: map_data,
|
131
|
+
realtime: false,
|
132
|
+
disable_fog: true,
|
133
|
+
record_replay: record_replay,
|
134
|
+
observed_player_id: 0,
|
135
|
+
options: Api::InterfaceOptions.new(
|
136
|
+
{
|
137
|
+
raw: true,
|
138
|
+
score: true,
|
139
|
+
feature_layer: feature_layer_interface_options(true),
|
140
|
+
show_cloaked: true,
|
141
|
+
show_burrowed_shadows: true,
|
142
|
+
show_placeholders: true,
|
143
|
+
raw_affects_selection: false,
|
144
|
+
raw_crop_to_playable_area: true
|
145
|
+
}.merge!(interface_options)
|
146
|
+
)
|
147
|
+
}.merge(options)
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Multiplayer only. Disconnects from a multiplayer game, equivalent to surrender. Keeps client alive.
|
152
|
+
def leave_game
|
153
|
+
send_request_for leave_game: Api::RequestLeaveGame.new
|
154
|
+
end
|
155
|
+
|
156
|
+
# Saves game to an in-memory bookmark.
|
157
|
+
def request_quick_save
|
158
|
+
send_request_for quick_save: Api::RequestQuickSave.new
|
159
|
+
end
|
160
|
+
|
161
|
+
# Loads from an in-memory bookmark.
|
162
|
+
def request_quick_load
|
163
|
+
send_request_for quick_load: Api::RequestQuickLoad.new
|
164
|
+
end
|
165
|
+
|
166
|
+
# Quits Sc2. Does not work on ladder.
|
167
|
+
def quit
|
168
|
+
send_request_for quit: Api::RequestQuit.new
|
169
|
+
end
|
170
|
+
|
171
|
+
# DURING GAME -
|
172
|
+
|
173
|
+
# // During Game
|
174
|
+
|
175
|
+
# Static data about the current game and map.
|
176
|
+
# @return [Api::ResponseGameInfo]
|
177
|
+
def game_info
|
178
|
+
send_request_for game_info: Api::RequestGameInfo.new
|
179
|
+
end
|
180
|
+
|
181
|
+
# Data about different gameplay elements. May be different for different games.
|
182
|
+
# Note that buff_id and effect_id gives worse quality data than generated from stableids (EffectId and BuffId)
|
183
|
+
# Those options are disabled by default
|
184
|
+
# @param ability_id [Boolean] to include ability data
|
185
|
+
# @param unit_type_id [Boolean] to include unit data
|
186
|
+
# @param upgrade_id [Boolean] to include upgrade data
|
187
|
+
# @param buff_id [Boolean] to get include buff data
|
188
|
+
# @param effect_id [Boolean] to get to include effect data
|
189
|
+
# @return [Api::ResponseData]
|
190
|
+
def data(ability_id: true, unit_type_id: true, upgrade_id: true, buff_id: true, effect_id: true)
|
191
|
+
send_request_for data: Api::RequestData.new(
|
192
|
+
ability_id:,
|
193
|
+
unit_type_id:,
|
194
|
+
upgrade_id:,
|
195
|
+
buff_id:,
|
196
|
+
effect_id:
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Snapshot of the current game state. Primary source for raw information
|
201
|
+
# @param game_loop [Integer] you wish to wait for (realtime only)
|
202
|
+
def observation(game_loop: nil)
|
203
|
+
# Sc2.logger.debug { "#{self.class}.#{__method__} game_loop: #{game_loop}" }
|
204
|
+
if game_loop.nil?
|
205
|
+
# Uncomment to enable multiple gc
|
206
|
+
# Async do
|
207
|
+
# result = Async do
|
208
|
+
|
209
|
+
@_cached_request_observation ||= Api::Request.new(
|
210
|
+
observation: Api::RequestObservation.new
|
211
|
+
).to_proto
|
212
|
+
@websocket.send_binary(@_cached_request_observation)
|
213
|
+
response = Api::Response.decode(@websocket.read.to_str)
|
214
|
+
|
215
|
+
if @status != response.status
|
216
|
+
@status = response.status
|
217
|
+
@listeners[StatusListener.name]&.each { _1.on_status_change(@status) }
|
218
|
+
end
|
219
|
+
|
220
|
+
response.observation
|
221
|
+
|
222
|
+
# Uncomment to enable manual GC
|
223
|
+
# end
|
224
|
+
|
225
|
+
# Async do
|
226
|
+
# # A step command is synchronous for both bots.
|
227
|
+
# # Bot A will wait for Bot B, then both get responses.
|
228
|
+
# # If we're ahead or even not, we can perform a minor GC sweep while we wait.
|
229
|
+
# # If the server notifies the other machine first
|
230
|
+
# # This smooths out unexpected hiccups and reduces overall major gc sweeps, possibly for free.
|
231
|
+
# begin
|
232
|
+
# GC.start(full_mark: false, immediate_sweep: true)
|
233
|
+
# # if rand(100).zero? # Just below every 5 seconds
|
234
|
+
# # GC.compact
|
235
|
+
# # end
|
236
|
+
# rescue
|
237
|
+
# # noop - just here for cleaner exceptions on interrupt
|
238
|
+
# end
|
239
|
+
# end
|
240
|
+
# result.wait
|
241
|
+
# end.wait
|
242
|
+
|
243
|
+
else
|
244
|
+
send_request_for observation: Api::RequestObservation.new(game_loop:)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Executes an array of [Api::Action] for a participant
|
249
|
+
# @param actions [Array<Api::Action>] to perform
|
250
|
+
# @return [Api::ResponseAction]
|
251
|
+
def action(actions)
|
252
|
+
send_request_for action: Api::RequestAction.new(
|
253
|
+
actions: actions
|
254
|
+
)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Executes an actions for an observer.
|
258
|
+
# @param actions [Array<Api::ObserverAction>]
|
259
|
+
def observer_action(actions)
|
260
|
+
# ActionObserverCameraMove camera_move = 2;
|
261
|
+
# ActionObserverCameraFollowPlayer camera_follow_player = 3;
|
262
|
+
send_request_for obs_action: Api::RequestObserverAction.new(
|
263
|
+
actions: actions
|
264
|
+
)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Moves observer camera to a position at a distance
|
268
|
+
# @param world_pos [Api::Point2D]
|
269
|
+
# @param distance [Float] Distance between camera and terrain. Larger value zooms out camera. Defaults to standard camera distance if set to 0.
|
270
|
+
def observer_action_camera_move(world_pos, distance = 0)
|
271
|
+
observer_action([Api::ObserverAction.new(
|
272
|
+
camera_move: Api::ActionObserverCameraMove.new(
|
273
|
+
world_pos:,
|
274
|
+
distance:
|
275
|
+
)
|
276
|
+
)])
|
277
|
+
end
|
278
|
+
|
279
|
+
# Advances the game simulation by step_count. Not used in realtime mode.
|
280
|
+
# Only constant step size supported - subsequent requests use cache.
|
281
|
+
def step(step_count = 1)
|
282
|
+
@_cached_request_step ||= Api::Request.new(
|
283
|
+
step: Api::RequestStep.new(count: step_count)
|
284
|
+
).to_proto
|
285
|
+
send_request_and_ignore(@_cached_request_step)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Additional methods for inspecting game state. Synchronous and must wait on response
|
289
|
+
# @param pathing [Array<Api::RequestQueryPathing>]
|
290
|
+
# @param abilities [Array<Api::RequestQueryAvailableAbilities>]
|
291
|
+
# @param placements [Array<Api::RequestQueryBuildingPlacement>]
|
292
|
+
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
293
|
+
# @return [Api::ResponseQuery]
|
294
|
+
def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: true)
|
295
|
+
send_request_for query: Api::RequestQuery.new(
|
296
|
+
pathing:,
|
297
|
+
abilities:,
|
298
|
+
placements:,
|
299
|
+
ignore_resource_requirements:
|
300
|
+
)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Queries one or more pathing queries
|
304
|
+
# @param queries [Array<Api::RequestQueryPathing>, Api::RequestQueryPathing] one or more pathing queries
|
305
|
+
# @return [Array<Api::ResponseQueryPathing>, Api::ResponseQueryPathing] one or more results depending on input size
|
306
|
+
def query_pathings(queries)
|
307
|
+
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
308
|
+
|
309
|
+
response = send_request_for query: Api::RequestQuery.new(
|
310
|
+
pathing: arr_queries
|
311
|
+
)
|
312
|
+
(arr_queries.size > 1) ? response.pathing : response.pathing.first
|
313
|
+
end
|
314
|
+
|
315
|
+
# Queries one or more ability-available checks
|
316
|
+
# @param queries [Array<Api::RequestQueryAvailableAbilities>, Api::RequestQueryAvailableAbilities] one or more pathing queries
|
317
|
+
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
318
|
+
# @return [Array<Api::ResponseQueryAvailableAbilities>, Api::ResponseQueryAvailableAbilities] one or more results depending on input size
|
319
|
+
def query_abilities(queries, ignore_resource_requirements: true)
|
320
|
+
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
321
|
+
|
322
|
+
response = send_request_for query: Api::RequestQuery.new(
|
323
|
+
abilities: arr_queries,
|
324
|
+
ignore_resource_requirements:
|
325
|
+
)
|
326
|
+
(arr_queries.size > 1) ? response.abilities : response.abilities.first
|
327
|
+
end
|
328
|
+
|
329
|
+
# Queries available abilities for units
|
330
|
+
# @param unit_tags [Array<Integer>, Integer] an array of unit tags or a single tag
|
331
|
+
# @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
|
332
|
+
# @return [Array<Api::ResponseQueryAvailableAbilities>, Api::ResponseQueryAvailableAbilities] one or more results depending on input size
|
333
|
+
def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: true)
|
334
|
+
queries = []
|
335
|
+
unit_tags = [unit_tags] unless unit_tags.is_a? Array
|
336
|
+
unit_tags.each do |unit_tag|
|
337
|
+
queries << Api::RequestQueryAvailableAbilities.new(unit_tag: unit_tag)
|
338
|
+
end
|
339
|
+
|
340
|
+
query_abilities(queries, ignore_resource_requirements:)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Queries one or more pathing queries
|
344
|
+
# @param queries [Array<Api::RequestQueryBuildingPlacement>, Api::RequestQueryBuildingPlacement] one or more placement queries
|
345
|
+
# @return [Array<Api::ResponseQueryBuildingPlacement>, Api::ResponseQueryBuildingPlacement] one or more results depending on input size
|
346
|
+
def query_placements(queries)
|
347
|
+
arr_queries = queries.is_a?(Array) ? queries : [queries]
|
348
|
+
|
349
|
+
response = query(placements: arr_queries)
|
350
|
+
|
351
|
+
(arr_queries.size > 1) ? response.placements : response.placements.first
|
352
|
+
end
|
353
|
+
|
354
|
+
# Generates a replay.
|
355
|
+
def save_replay
|
356
|
+
send_request_for save_replay: Api::RequestSaveReplay.new
|
357
|
+
end
|
358
|
+
|
359
|
+
# MapCommand does not actually gracefully trigger start/restart
|
360
|
+
# RequestMapCommand map_command = 22; // Execute a particular trigger through a string interface
|
361
|
+
|
362
|
+
# Returns metadata about a replay file. Does not load the replay.
|
363
|
+
# RequestReplayInfo replay_info = 16; //
|
364
|
+
# @param replay_path [String] path to replay
|
365
|
+
# @param replay_data [String] alternative to file, binary string of replay_file.read
|
366
|
+
# @param download_data [String] if true, ensure the data and binary are downloaded if this is an old version replay.
|
367
|
+
# @return [Api::ResponseReplayInfo]
|
368
|
+
def replay_info(replay_path: nil, replay_data: nil, download_data: false)
|
369
|
+
raise Sc2::Error, "Missing replay." if replay_data.nil? && replay_path.nil?
|
370
|
+
|
371
|
+
send_request_for replay_info: Api::RequestReplayInfo.new(
|
372
|
+
replay_path: replay_path.to_s,
|
373
|
+
replay_data: replay_data,
|
374
|
+
download_data: download_data
|
375
|
+
)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Returns directory of maps that can be played on.
|
379
|
+
# @return [Api::ResponseAvailableMaps] which has #local_map_paths and #battlenet_map_names arrays
|
380
|
+
def available_maps
|
381
|
+
send_request_for available_maps: Api::RequestAvailableMaps.new
|
382
|
+
end
|
383
|
+
|
384
|
+
# Saves binary map data to the local temp directory.
|
385
|
+
def save_map
|
386
|
+
send_request_for save_map: Api::RequestSaveMap.new
|
387
|
+
end
|
388
|
+
|
389
|
+
# Network ping for testing connection.
|
390
|
+
def ping
|
391
|
+
send_request_for ping: Api::RequestPing.new
|
392
|
+
end
|
393
|
+
|
394
|
+
# Display debug information and execute debug actions
|
395
|
+
# @param commands [Array<Api::DebugCommand>]
|
396
|
+
# @return [void]
|
397
|
+
def debug(commands)
|
398
|
+
send_request_for debug: Api::RequestDebug.new(
|
399
|
+
debug: commands
|
400
|
+
)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Sends request for type and returns response that type, i.e.
|
404
|
+
# send_request_for(observation: RequestObservation)
|
405
|
+
# Is identical to
|
406
|
+
# send_request(
|
407
|
+
# Api::Request.new(observation: RequestObservation)
|
408
|
+
# )[:observation]
|
409
|
+
def send_request_for(**kwargs)
|
410
|
+
response = send_request(Api::Request.new(kwargs))
|
411
|
+
response[kwargs.keys.first.to_s]
|
412
|
+
end
|
413
|
+
|
414
|
+
private
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Connection
|
5
|
+
# Callbacks when game status changes
|
6
|
+
module StatusListener
|
7
|
+
# Called when game status changes
|
8
|
+
# @param status [:launched, :in_game, :in_replay, :ended, :quit, :unknown] game state, i.e. :in_game, :ended, :launched
|
9
|
+
# noinspection
|
10
|
+
def on_status_change(status)
|
11
|
+
Sc2.logger.debug { "#{self.class}.#{__method__} #{status}" }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|