mirror of
https://github.com/cyberarm/w3d_hub_linux_launcher.git
synced 2025-12-16 09:12:35 +00:00
Added support for pinging servers, server list now reorders containers instead of recreating all of them for every refresh, server list updater should restart on crash
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -11,6 +11,7 @@ gem "async-http"
|
|||||||
gem "async-websocket"
|
gem "async-websocket"
|
||||||
gem "thread-local"
|
gem "thread-local"
|
||||||
gem "ircparser"
|
gem "ircparser"
|
||||||
|
gem "net-ping"
|
||||||
|
|
||||||
# group :windows_packaging do
|
# group :windows_packaging do
|
||||||
# gem "rake"
|
# gem "rake"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class W3DHub
|
class W3DHub
|
||||||
class Api
|
class Api
|
||||||
class ServerListServer
|
class ServerListServer
|
||||||
attr_reader :id, :game, :address, :port, :region, :channel, :status
|
attr_reader :id, :game, :address, :port, :region, :channel, :ping, :status
|
||||||
|
|
||||||
def initialize(hash)
|
def initialize(hash)
|
||||||
@data = hash
|
@data = hash
|
||||||
@@ -12,8 +12,12 @@ class W3DHub
|
|||||||
@port = @data[:port]
|
@port = @data[:port]
|
||||||
@region = @data[:region]
|
@region = @data[:region]
|
||||||
@channel = @data[:channel] || "release"
|
@channel = @data[:channel] || "release"
|
||||||
|
@ping = -1
|
||||||
|
|
||||||
@status = @data[:status] ? Status.new(@data[:status]) : nil
|
@status = @data[:status] ? Status.new(@data[:status]) : nil
|
||||||
|
|
||||||
|
@last_pinged = -1
|
||||||
|
@ping_interval = 30
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(hash)
|
def update(hash)
|
||||||
@@ -29,6 +33,17 @@ class W3DHub
|
|||||||
@status.instance_variable_set(:@teams, hash[:teams]&.map { |t| Team.new(t) }) if hash[:teams]
|
@status.instance_variable_set(:@teams, hash[:teams]&.map { |t| Team.new(t) }) if hash[:teams]
|
||||||
@status.instance_variable_set(:@players, hash[:players]&.select { |t| t[:nick] != "Nod" && t[:nick] != "GDI" }&.map { |t| Player.new(t) }) if hash[:players]
|
@status.instance_variable_set(:@players, hash[:players]&.select { |t| t[:nick] != "Nod" && t[:nick] != "GDI" }&.map { |t| Player.new(t) }) if hash[:players]
|
||||||
|
|
||||||
|
if Gosu.milliseconds - @last_pinged >= @ping_interval
|
||||||
|
@last_pinged = Gosu.milliseconds
|
||||||
|
|
||||||
|
Thread.new do
|
||||||
|
ping = Net::Ping::External.new(@address)
|
||||||
|
@ping = (ping.duration * 1000.0).round if ping.ping?
|
||||||
|
|
||||||
|
States::Interface.instance&.update_server_ping(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -77,53 +77,65 @@ class W3DHub
|
|||||||
|
|
||||||
def run
|
def run
|
||||||
Thread.new do
|
Thread.new do
|
||||||
Async do |task|
|
begin
|
||||||
internet = Async::HTTP::Internet.instance
|
connect
|
||||||
|
rescue => e
|
||||||
|
puts e
|
||||||
|
puts e.backtrace
|
||||||
|
|
||||||
logger.debug(LOG_TAG) { "Requesting connection token..." }
|
sleep 10
|
||||||
response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""])
|
retry
|
||||||
data = JSON.parse(response.read, symbolize_names: true)
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
id = data[:connectionToken]
|
def connect
|
||||||
endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
|
Async do |task|
|
||||||
|
internet = Async::HTTP::Internet.instance
|
||||||
|
|
||||||
logger.debug(LOG_TAG) { "Connecting to websocket..." }
|
logger.debug(LOG_TAG) { "Requesting connection token..." }
|
||||||
Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection|
|
response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""])
|
||||||
logger.debug(LOG_TAG) { "Requesting json protocol, v1..." }
|
data = JSON.parse(response.read, symbolize_names: true)
|
||||||
connection.write({ protocol: "json", version: 1 })
|
|
||||||
connection.flush
|
|
||||||
logger.debug(LOG_TAG) { "Received: #{connection.read}" }
|
|
||||||
logger.debug(LOG_TAG) { "Sending \"PING\"(?)" }
|
|
||||||
connection.write({ "type": 6 })
|
|
||||||
|
|
||||||
logger.debug(LOG_TAG) { "Subscribing to server changes..." }
|
id = data[:connectionToken]
|
||||||
Store.server_list.each_with_index do |server, i|
|
endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
|
||||||
i += 1
|
|
||||||
mode = 1 # 2 full details, 1 basic details
|
|
||||||
out = { "type": 1, "invocationId": "#{i}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, mode] }
|
|
||||||
connection.write(out)
|
|
||||||
end
|
|
||||||
|
|
||||||
logger.debug(LOG_TAG) { "Waiting for data..." }
|
logger.debug(LOG_TAG) { "Connecting to websocket..." }
|
||||||
while (message = connection.read)
|
Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection|
|
||||||
connection.write({ type: 6 }) if message.first[:type] == 6
|
logger.debug(LOG_TAG) { "Requesting json protocol, v1..." }
|
||||||
|
connection.write({ protocol: "json", version: 1 })
|
||||||
|
connection.flush
|
||||||
|
logger.debug(LOG_TAG) { "Received: #{connection.read}" }
|
||||||
|
logger.debug(LOG_TAG) { "Sending \"PING\"(?)" }
|
||||||
|
connection.write({ "type": 6 })
|
||||||
|
|
||||||
if message&.first&.fetch(:type) == 1
|
logger.debug(LOG_TAG) { "Subscribing to server changes..." }
|
||||||
message.each do |rpc|
|
Store.server_list.each_with_index do |server, i|
|
||||||
next unless rpc[:target] == "ServerStatusChanged"
|
i += 1
|
||||||
|
mode = 1 # 2 full details, 1 basic details
|
||||||
|
out = { "type": 1, "invocationId": "#{i}", "target": "SubscribeToServerStatusUpdates", "arguments": [server.id, mode] }
|
||||||
|
connection.write(out)
|
||||||
|
end
|
||||||
|
|
||||||
id, data = rpc[:arguments]
|
logger.debug(LOG_TAG) { "Waiting for data..." }
|
||||||
server = Store.server_list.find { |s| s.id == id }
|
while (message = connection.read)
|
||||||
server_updated = server&.update(data)
|
connection.write({ type: 6 }) if message.first[:type] == 6
|
||||||
States::Interface.instance&.update_server_browser(server) if server_updated
|
|
||||||
end
|
if message&.first&.fetch(:type) == 1
|
||||||
|
message.each do |rpc|
|
||||||
|
next unless rpc[:target] == "ServerStatusChanged"
|
||||||
|
|
||||||
|
id, data = rpc[:arguments]
|
||||||
|
server = Store.server_list.find { |s| s.id == id }
|
||||||
|
server_updated = server&.update(data)
|
||||||
|
States::Interface.instance&.update_server_browser(server) if server_updated
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ensure
|
|
||||||
logger.debug(LOG_TAG) { "Cleaning up..." }
|
|
||||||
@@instance = nil
|
|
||||||
end
|
end
|
||||||
|
ensure
|
||||||
|
logger.debug(LOG_TAG) { "Cleaning up..." }
|
||||||
|
@@instance = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class W3DHub
|
|||||||
|
|
||||||
Store.applications.games.each { |game| @filters[game.id.to_sym] = true if @filters[game.id.to_sym].nil? }
|
Store.applications.games.each { |game| @filters[game.id.to_sym] = true if @filters[game.id.to_sym].nil? }
|
||||||
|
|
||||||
|
@ping_icons = {}
|
||||||
|
generate_ping_icons
|
||||||
|
|
||||||
body.clear do
|
body.clear do
|
||||||
stack(width: 1.0, height: 1.0, padding: 8) do
|
stack(width: 1.0, height: 1.0, padding: 8) do
|
||||||
stack(width: 1.0, height: 18) do
|
stack(width: 1.0, height: 18) do
|
||||||
@@ -142,7 +145,8 @@ class W3DHub
|
|||||||
if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list
|
if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list
|
||||||
@refresh_server_list = nil
|
@refresh_server_list = nil
|
||||||
|
|
||||||
populate_server_list
|
# populate_server_list
|
||||||
|
reorder_server_list
|
||||||
|
|
||||||
if @selected_server&.id == @refresh_server&.id
|
if @selected_server&.id == @refresh_server&.id
|
||||||
if @refresh_server
|
if @refresh_server
|
||||||
@@ -158,11 +162,79 @@ class W3DHub
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate_ping_icons
|
||||||
|
signal3 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal3.png")
|
||||||
|
signal2 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal2.png")
|
||||||
|
signal1 = get_image("#{GAME_ROOT_PATH}/media/ui_icons/signal1.png")
|
||||||
|
question = get_image("#{GAME_ROOT_PATH}/media/ui_icons/question.png")
|
||||||
|
|
||||||
|
good = Gosu.render(signal3.width, signal3.height) do
|
||||||
|
signal3.draw(0, 0, 0, 1, 1, 0xff_008000)
|
||||||
|
end
|
||||||
|
|
||||||
|
fair = Gosu.render(signal3.width, signal3.height) do
|
||||||
|
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
|
||||||
|
signal2.draw(0, 0, 0, 1, 1, 0xff_804000)
|
||||||
|
end
|
||||||
|
|
||||||
|
poor = Gosu.render(signal3.width, signal3.height) do
|
||||||
|
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
|
||||||
|
signal1.draw(0, 0, 0, 1, 1, 0xff_800000)
|
||||||
|
end
|
||||||
|
|
||||||
|
bad = Gosu.render(signal3.width, signal3.height) do
|
||||||
|
signal3.draw(0, 0, 0, 1, 1, 0xff_444444)
|
||||||
|
end
|
||||||
|
|
||||||
|
unknown = Gosu.render(signal3.width, signal3.height) do
|
||||||
|
signal3.draw(0, 0, 0, 1, 1, 0xff_222222)
|
||||||
|
question.draw(0, 0, 0, 1, 1, 0xff_888888)
|
||||||
|
end
|
||||||
|
|
||||||
|
@ping_icons[:good] = good
|
||||||
|
@ping_icons[:fair] = fair
|
||||||
|
@ping_icons[:poor] = poor
|
||||||
|
@ping_icons[:bad] = bad
|
||||||
|
@ping_icons[:unknown] = unknown
|
||||||
|
end
|
||||||
|
|
||||||
|
def ping_icon(ping)
|
||||||
|
case ping
|
||||||
|
when 0..160
|
||||||
|
@ping_icons[:good]
|
||||||
|
when 161..250
|
||||||
|
@ping_icons[:fair]
|
||||||
|
when 251..1_000
|
||||||
|
@ping_icons[:poor]
|
||||||
|
when 1_001..5_000
|
||||||
|
@ping_icons[:bad]
|
||||||
|
else
|
||||||
|
@ping_icons[:unknown]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def refresh_server_list(server)
|
def refresh_server_list(server)
|
||||||
@refresh_server_list = Gosu.milliseconds + 3_000
|
@refresh_server_list = Gosu.milliseconds + 3_000
|
||||||
@refresh_server = server if @selected_server&.id == server.id
|
@refresh_server = server if @selected_server&.id == server.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_server_ping(server)
|
||||||
|
container = @server_list_container.children.find do |child|
|
||||||
|
child.style.tag == server.id
|
||||||
|
end
|
||||||
|
|
||||||
|
if container
|
||||||
|
ping_image = container.children.map { |c| c.children }.flatten.find do |child|
|
||||||
|
child.style.tag == :ping
|
||||||
|
end
|
||||||
|
|
||||||
|
if ping_image
|
||||||
|
ping_image.value = ping_icon(server.ping)
|
||||||
|
ping_image.tip = "#{server.ping}ms"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def stylize_selected_server(server_container)
|
def stylize_selected_server(server_container)
|
||||||
server_container.style.server_item_background = server_container.style.default[:background]
|
server_container.style.server_item_background = server_container.style.default[:background]
|
||||||
server_container.style.server_item_hover_background = server_container.style.hover[:background]
|
server_container.style.server_item_hover_background = server_container.style.hover[:background]
|
||||||
@@ -175,6 +247,19 @@ class W3DHub
|
|||||||
server_container.style.active[:background] = @selected_color
|
server_container.style.active[:background] = @selected_color
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reorder_server_list
|
||||||
|
@server_list_container.children.sort_by! do |child|
|
||||||
|
s = Store.server_list.find { |s| s.id == child.style.tag }
|
||||||
|
|
||||||
|
[s&.status&.player_count, s&.id]
|
||||||
|
end.reverse!.each_with_index do |child, i|
|
||||||
|
child.style.background = 0xff_333333 if i.even?
|
||||||
|
child.style.background = 0 if i.odd?
|
||||||
|
end
|
||||||
|
|
||||||
|
@server_list_container.recalculate
|
||||||
|
end
|
||||||
|
|
||||||
def populate_server_list
|
def populate_server_list
|
||||||
Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list
|
Store.server_list = Store.server_list.sort_by! { |s| [s&.status&.player_count, s&.id] }.reverse if Store.server_list
|
||||||
|
|
||||||
@@ -188,41 +273,34 @@ class W3DHub
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
server_container = flow(width: 1.0, height: 48, hover: { background: 0xff_555566 }, active: { background: 0xff_555588 }) do
|
server_container = flow(width: 1.0, height: 48, hover: { background: 0xff_555566 }, active: { background: 0xff_555588 }, tag: server.id) do
|
||||||
background 0xff_333333 if i.even?
|
background 0xff_333333 if i.even?
|
||||||
|
|
||||||
flow(width: 48, height: 1.0, padding: 4) do
|
flow(width: 48, height: 1.0, padding: 4) do
|
||||||
image game_icon(server), height: 1.0
|
image game_icon(server), height: 1.0, tag: :game_icon
|
||||||
end
|
end
|
||||||
|
|
||||||
stack(width: 0.45, height: 1.0) do
|
stack(width: 0.45, height: 1.0) do
|
||||||
inscription "<b>#{server&.status&.name}</b>"
|
inscription "<b>#{server&.status&.name}</b>", tag: :server_name
|
||||||
|
|
||||||
flow(width: 1.0, height: 1.0) do
|
flow(width: 1.0, height: 1.0) do
|
||||||
inscription server.channel, margin_right: 64, text_size: 14
|
inscription server.channel, margin_right: 64, text_size: 14, tag: :server_channel
|
||||||
inscription server.region, text_size: 14
|
inscription server.region, text_size: 14, tag: :server_region
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
flow(fill: true, height: 1.0) do
|
flow(fill: true, height: 1.0) do
|
||||||
inscription "#{server&.status&.map}"
|
inscription "#{server&.status&.map}", tag: :map_name
|
||||||
end
|
end
|
||||||
|
|
||||||
flow(width: 0.11, height: 1.0) do
|
flow(width: 0.11, height: 1.0) do
|
||||||
inscription "#{server&.status&.player_count}/#{server&.status&.max_players}"
|
inscription "#{server&.status&.player_count}/#{server&.status&.max_players}", tag: :player_count
|
||||||
end
|
end
|
||||||
|
|
||||||
# case rand(0..478)
|
|
||||||
# when 0..60
|
|
||||||
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal3.png", width: 0.05, color: 0xff_008000
|
|
||||||
# when 61..160
|
|
||||||
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal2.png", width: 0.05, color: 0xff_804000
|
|
||||||
# else
|
|
||||||
# image "#{GAME_ROOT_PATH}/media/ui_icons/signal1.png", width: 0.05, color: 0xff_800000
|
|
||||||
# end
|
|
||||||
|
|
||||||
flow(width: 48, height: 1.0, padding: 4) do
|
flow(width: 48, height: 1.0, padding: 4) do
|
||||||
image "#{GAME_ROOT_PATH}/media/ui_icons/question.png", height: 1.0, color: 0xff_444444
|
puts "#{server&.status&.name}#{server.ping}"
|
||||||
|
|
||||||
|
image ping_icon(server.ping), height: 1.0, tip: "#{server.ping}ms", tag: :ping
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,12 @@ class W3DHub
|
|||||||
@page.refresh_server_list(server)
|
@page.refresh_server_list(server)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_server_ping(server)
|
||||||
|
return unless @page.is_a?(Pages::ServerBrowser)
|
||||||
|
|
||||||
|
@page.update_server_ping(server)
|
||||||
|
end
|
||||||
|
|
||||||
def show_application_taskbar
|
def show_application_taskbar
|
||||||
@application_taskbar_container.show
|
@application_taskbar_container.show
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ require "async/http/internet/instance"
|
|||||||
require "async/http/endpoint"
|
require "async/http/endpoint"
|
||||||
require "async/websocket/client"
|
require "async/websocket/client"
|
||||||
require "protocol/websocket/connection"
|
require "protocol/websocket/connection"
|
||||||
|
require "net/ping"
|
||||||
|
|
||||||
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
|
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
|
||||||
I18n.default_locale = :en
|
I18n.default_locale = :en
|
||||||
|
|||||||
Reference in New Issue
Block a user