diff --git a/Gemfile b/Gemfile
index b669e32..b62c3b0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +11,7 @@ gem "async-http"
gem "async-websocket"
gem "thread-local"
gem "ircparser"
+gem "net-ping"
# group :windows_packaging do
# gem "rake"
diff --git a/lib/api/server_list_server.rb b/lib/api/server_list_server.rb
index b7dac07..a75aee1 100644
--- a/lib/api/server_list_server.rb
+++ b/lib/api/server_list_server.rb
@@ -1,7 +1,7 @@
class W3DHub
class Api
class ServerListServer
- attr_reader :id, :game, :address, :port, :region, :channel, :status
+ attr_reader :id, :game, :address, :port, :region, :channel, :ping, :status
def initialize(hash)
@data = hash
@@ -12,8 +12,12 @@ class W3DHub
@port = @data[:port]
@region = @data[:region]
@channel = @data[:channel] || "release"
+ @ping = -1
@status = @data[:status] ? Status.new(@data[:status]) : nil
+
+ @last_pinged = -1
+ @ping_interval = 30
end
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(:@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
end
diff --git a/lib/api/server_list_updater.rb b/lib/api/server_list_updater.rb
index 35aaf26..67e8082 100644
--- a/lib/api/server_list_updater.rb
+++ b/lib/api/server_list_updater.rb
@@ -77,53 +77,65 @@ class W3DHub
def run
Thread.new do
- Async do |task|
- internet = Async::HTTP::Internet.instance
+ begin
+ connect
+ rescue => e
+ puts e
+ puts e.backtrace
- logger.debug(LOG_TAG) { "Requesting connection token..." }
- response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""])
- data = JSON.parse(response.read, symbolize_names: true)
+ sleep 10
+ retry
+ end
+ end
+ end
- id = data[:connectionToken]
- endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
+ def connect
+ Async do |task|
+ internet = Async::HTTP::Internet.instance
- logger.debug(LOG_TAG) { "Connecting to websocket..." }
- Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection|
- 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 })
+ logger.debug(LOG_TAG) { "Requesting connection token..." }
+ response = internet.post("https://gsh.w3dhub.com/listings/push/v2/negotiate?negotiateVersion=1", Api::DEFAULT_HEADERS, [""])
+ data = JSON.parse(response.read, symbolize_names: true)
- logger.debug(LOG_TAG) { "Subscribing to server changes..." }
- Store.server_list.each_with_index do |server, i|
- 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[:connectionToken]
+ endpoint = Async::HTTP::Endpoint.parse("https://gsh.w3dhub.com/listings/push/v2?id=#{id}", alpn_protocols: Async::HTTP::Protocol::HTTP11.names)
- logger.debug(LOG_TAG) { "Waiting for data..." }
- while (message = connection.read)
- connection.write({ type: 6 }) if message.first[:type] == 6
+ logger.debug(LOG_TAG) { "Connecting to websocket..." }
+ Async::WebSocket::Client.connect(endpoint, headers: Api::DEFAULT_HEADERS, handler: PatchedConnection) do |connection|
+ 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
- message.each do |rpc|
- next unless rpc[:target] == "ServerStatusChanged"
+ logger.debug(LOG_TAG) { "Subscribing to server changes..." }
+ Store.server_list.each_with_index do |server, i|
+ 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]
- 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
+ logger.debug(LOG_TAG) { "Waiting for data..." }
+ while (message = connection.read)
+ connection.write({ type: 6 }) if message.first[:type] == 6
+
+ 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
- ensure
- logger.debug(LOG_TAG) { "Cleaning up..." }
- @@instance = nil
end
+ ensure
+ logger.debug(LOG_TAG) { "Cleaning up..." }
+ @@instance = nil
end
end
end
diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb
index 6ca521b..e24d794 100644
--- a/lib/pages/server_browser.rb
+++ b/lib/pages/server_browser.rb
@@ -15,6 +15,9 @@ class W3DHub
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
stack(width: 1.0, height: 1.0, padding: 8) do
stack(width: 1.0, height: 18) do
@@ -142,7 +145,8 @@ class W3DHub
if @refresh_server_list && Gosu.milliseconds >= @refresh_server_list
@refresh_server_list = nil
- populate_server_list
+ # populate_server_list
+ reorder_server_list
if @selected_server&.id == @refresh_server&.id
if @refresh_server
@@ -158,11 +162,79 @@ class W3DHub
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)
@refresh_server_list = Gosu.milliseconds + 3_000
@refresh_server = server if @selected_server&.id == server.id
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)
server_container.style.server_item_background = server_container.style.default[: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
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
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
- 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?
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
stack(width: 0.45, height: 1.0) do
- inscription "#{server&.status&.name}"
+ inscription "#{server&.status&.name}", tag: :server_name
flow(width: 1.0, height: 1.0) do
- inscription server.channel, margin_right: 64, text_size: 14
- inscription server.region, text_size: 14
+ inscription server.channel, margin_right: 64, text_size: 14, tag: :server_channel
+ inscription server.region, text_size: 14, tag: :server_region
end
end
flow(fill: true, height: 1.0) do
- inscription "#{server&.status&.map}"
+ inscription "#{server&.status&.map}", tag: :map_name
end
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
- # 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
- 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
diff --git a/lib/states/interface_redesign.rb b/lib/states/interface_redesign.rb
index 15997cd..925854e 100644
--- a/lib/states/interface_redesign.rb
+++ b/lib/states/interface_redesign.rb
@@ -165,6 +165,12 @@ class W3DHub
@page.refresh_server_list(server)
end
+ def update_server_ping(server)
+ return unless @page.is_a?(Pages::ServerBrowser)
+
+ @page.update_server_ping(server)
+ end
+
def show_application_taskbar
@application_taskbar_container.show
end
diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb
index c845522..5d4b15d 100644
--- a/w3d_hub_linux_launcher.rb
+++ b/w3d_hub_linux_launcher.rb
@@ -50,6 +50,7 @@ require "async/http/internet/instance"
require "async/http/endpoint"
require "async/websocket/client"
require "protocol/websocket/connection"
+require "net/ping"
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en