From 89f7f4ab6a8368590a96f81cf63abdbedf85b87b Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Fri, 19 Nov 2021 15:48:13 -0600 Subject: [PATCH] Can now join a server and launch installed games (Play Now does nothing special yet) --- lib/application_manager.rb | 11 +++++ lib/application_manager/tasks/importer.rb | 4 +- lib/pages/community.rb | 52 ++++++++------------ lib/pages/server_browser.rb | 55 ++++++++++++++++++++-- lib/settings.rb | 6 ++- lib/states/prompt_dialog.rb | 52 ++++++++++++++++++++ media/social_media_icons/discord.png | Bin 0 -> 3863 bytes media/social_media_icons/facebook.png | Bin 0 -> 17490 bytes w3dhub.rb | 1 + 9 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 lib/states/prompt_dialog.rb create mode 100644 media/social_media_icons/discord.png create mode 100644 media/social_media_icons/facebook.png diff --git a/lib/application_manager.rb b/lib/application_manager.rb index 29a4422..a73a388 100644 --- a/lib/application_manager.rb +++ b/lib/application_manager.rb @@ -83,6 +83,17 @@ class W3DHub end end + def join_server(app_id, channel, server, password = nil) + if installed?(app_id, channel) && window.settings[:server_list_username].to_s.length.positive? + run( + app_id, channel, + "+connect #{server.address}:#{server.port}", + "+netplayername \"#{window.settings[:server_list_username]}\"", + password ? "+password \"#{password}\"" : "" + ) + end + end + def installed!(task) # install_dir # installed_version diff --git a/lib/application_manager/tasks/importer.rb b/lib/application_manager/tasks/importer.rb index e18ce62..4506240 100644 --- a/lib/application_manager/tasks/importer.rb +++ b/lib/application_manager/tasks/importer.rb @@ -1,11 +1,11 @@ class W3DHub class ApplicationManager class Importer < Task - def initialize(app_id, channel, path) + def initialize(app_id, channel, path = nil) super(app_id, channel) @path = path end end end -end \ No newline at end of file +end diff --git a/lib/pages/community.rb b/lib/pages/community.rb index 8e6f8e8..1a87032 100644 --- a/lib/pages/community.rb +++ b/lib/pages/community.rb @@ -5,54 +5,40 @@ class W3DHub body.clear do stack(width: 1.0, height: 1.0, padding: 8) do stack(width: 1.0, height: 0.15) do - tagline "Welcome to the W3D Hub Launcher" - para "The W3D Hub launcher is a one-stop shop for your W3D gamings needs, providing game downloads and automatic updating, an intregrated server browser, centralized management of in-game options and many other features." + tagline "Welcome to the #{W3DHub::NAME}" + para "The #{W3DHub::NAME} is a one-stop shop for your W3D gaming needs, providing game downloads, automatic updating, an integrated server browser, and centralized management of in-game options." end - flow(width: 1.0, height: 0.1, margin_top: 24) do - flow(width: 0.375, height: 1.0) do + flow(width: 1.0, height: 0.15, margin_bottom: 24) do + flow(width: (1.0 - 0.27) / 2, height: 1.0) do end - flow(width: 0.25, height: 1.0) do - image "#{GAME_ROOT_PATH}/media/icons/apb.png", height: 1.0 - image "#{GAME_ROOT_PATH}/media/icons/ren.png", height: 1.0, margin_left: 32 - image "#{GAME_ROOT_PATH}/media/icons/tsr.png", height: 1.0, margin_left: 32 - image "#{GAME_ROOT_PATH}/media/icons/w3dhub.png", height: 1.0, margin_left: 32 - end - - flow(width: 0.375, height: 1.0) do + flow(width: 0.27, height: 1.0) do + image "#{GAME_ROOT_PATH}/media/icons/w3dhub.png", height: 1.0, hover: { color: 0xaa_ffffff }, tip: "W3D Hub Forums" do + Launchy.open("https://w3dhub.com/forum/") + end + image "#{GAME_ROOT_PATH}/media/social_media_icons/discord.png", hover: { color: 0xaa_ffffff }, height: 1.0, margin_left: 32, tip: "W3D Hub Discord Server" do + Launchy.open("https://discord.com/invite/GYhW7eV") + end + image "#{GAME_ROOT_PATH}/media/social_media_icons/facebook.png", hover: { color: 0xaa_ffffff }, height: 1.0, margin_left: 32, tip: "W3D Hub Facebook Page" do + Launchy.open("https://www.facebook.com/w3dhub") + end end end - stack(width: 1.0, height: 0.6, scroll: true) do + stack(width: 1.0, height: 0.55, scroll: true) do tagline "Latest Updates" - para "Beta 12", margin_left: 16 - para "- Server Browser: Added detailed information for selection server", margin_left: 32 - - para "Beta 11.6", margin_left: 16, margin_top: 16 - para "- Localisation: Added Korean translations (unknown author)", margin_left: 32 - para "- Localisation: Added Spanish translations (thanks to Silverlight and URKA)", margin_left: 32 - para "- Localisation: Added Spanish translations (thanks to darkyuri-cz)", margin_left: 32 - - para "Beta 11.0", margin_left: 16, margin_top: 16 - para "- Localisation: Added partial Chinese (Simplified) translations and Polish (thanks to DoDoCat and TrollekPL on the W3D Hub forums for providing translations)", margin_left: 32 - para "- Performance: Reduced CPU and GPU usage during game installs and updates", margin_left: 32 - para "- Settings: Added new setting menu for the launcher - click on the [gear] icon in the titlebar. Incluudes:", margin_left: 32 - para "- Manually choose language, rather than using default based on OS", margin_left: 48 - para "- Choose package cache folder location", margin_left: 48 - para "- Choose default folder into which games are installed", margin_left: 48 - para "- Server Browser: Now receives push notifications so it shows changes to maps, player counts, etc. as soon as they are available", margin_left: 32 - para "- Server Browser: Now lists servers with players in above empty ones", margin_left: 32 - para "- Server Browser: Game filter options are now saved", margin_left: 32 + para "Hello World " * 100 end - stack(width: 1.0, height: 0.15) do + stack(width: 1.0, height: 0.15, margin_top: 16) do tagline "Help & Support" flow(width: 1.0) do para "For help and support using this launcher or playing any W3D Hub game visit the" link("W3D Hub forums", text_size: 16) { Launchy.open("https://w3dhub.com/forum/") } para "or join us in" - link("[discord]#tech-support", text_size: 16) { Launchy.open("https://w3dhub.com/forum/") } + image "#{GAME_ROOT_PATH}/media/social_media_icons/discord.png", height: 16, padding_top: 4 + link("#tech-support", text_size: 16) { Launchy.open("https://discord.com/invite/GYhW7eV") } para "on the W3D Hub Discord server" end end diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index 688fc5b..16e327d 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -47,9 +47,18 @@ class W3DHub end flow(width: 0.249, height: 1.0) do - inscription "Nickname:" - inscription "Cyberarm" - image "#{GAME_ROOT_PATH}/media/ui_icons/wrench.png", height: 16 + inscription "Nickname:", width: 0.32 + @nickname_label = inscription "#{window.settings[:server_list_username]}", width: 0.6 + image "#{GAME_ROOT_PATH}/media/ui_icons/wrench.png", height: 16, hover: { color: 0xaa_ffffff }, tip: "Set nickname" do + # Prompt for player name + prompt_for_nickname( + accept_callback: proc do |entry| + @nickname_label.value = entry + window.settings[:server_list_username] = entry + window.settings.save_settings + end + ) + end end end @@ -180,7 +189,33 @@ class W3DHub end stack(width: 1.0, height: 0.25) do - button "Join Server", enabled: window.application_manager.installed?(server.game, window.applications.games.find { |g| g.id == server.game }.channels.first) + game_installed = window.application_manager.installed?(server.game, window.applications.games.find { |g| g.id == server.game }.channels.first.id) + + button "Join Server", enabled: !game_installed.nil? do + # Check for nickname + # prompt for nickname + # !abort unless nickname set + # Launch game + if window.settings[:server_list_username].to_s.length.zero? + prompt_for_nickname( + accept_callback: proc do |entry| + @nickname_label.value = entry + window.settings[:server_list_username] = entry + window.settings.save_settings + + window.application_manager.join_server( + server.game, + window.applications.games.find { |g| g.id == server.game }.channels.first.id, server + ) + end + ) + else + window.application_manager.join_server( + server.game, + window.applications.games.find { |g| g.id == server.game }.channels.first.id, server + ) + end + end end stack(width: 1.0, height: 0.55, margin_top: 16) do @@ -281,6 +316,18 @@ class W3DHub "C&C Renegade" end end + + def prompt_for_nickname(accept_callback: nil, cancel_callback: nil) + push_state( + W3DHub::States::PromptDialog, + title: "Set Nickname", + message: "Set a nickname that will be used when joining a server:", + prefill: window.settings[:server_list_username], + accept_callback: accept_callback, + cancel_callback: cancel_callback, + valid_callback: proc { |entry| entry.length.positive? } + ) + end end end end diff --git a/lib/settings.rb b/lib/settings.rb index 783ce2a..0ef73d8 100644 --- a/lib/settings.rb +++ b/lib/settings.rb @@ -8,7 +8,7 @@ class W3DHub wine_command: "wine", create_wine_prefixes: true, allow_diagnostic_reports: false, - server_list_username: nil, + server_list_username: "", account: {}, applications: {}, games: {} @@ -53,6 +53,10 @@ class W3DHub @settings.dig(*args) end + def []=(key, value) + @settings[key] = value + end + def load_settings @settings = JSON.parse(File.read(SETTINGS_FILE_PATH), symbolize_names: true) end diff --git a/lib/states/prompt_dialog.rb b/lib/states/prompt_dialog.rb new file mode 100644 index 0000000..41cf118 --- /dev/null +++ b/lib/states/prompt_dialog.rb @@ -0,0 +1,52 @@ +class W3DHub + class States + class PromptDialog < CyberarmEngine::GuiState + def setup + window.show_cursor = true + + theme(W3DHub::THEME) + + background 0xee_444444 + + stack(width: 1.0, height: 1.0, margin: 128, background: 0xee_222222) do + flow(width: 1.0, height: 0.1, padding: 8) do + background 0x88_000000 + + image "#{GAME_ROOT_PATH}/media/ui_icons/question.png", width: 0.04, align: :center, color: 0xff_ff8800 + + tagline "#{@options[:title]}", width: 0.9, text_align: :center + end + + stack(width: 1.0, height: 0.78, padding: 16) do + para @options[:message], width: 1.0 + @prompt_entry = edit_line @options[:prefill].to_s, margin_top: 24, width: 1.0, focus: true + end + + flow(width: 1.0, height: 0.1, padding: 8) do + button "Cancel", width: 0.25 do + pop_state + @options[:cancel_callback]&.call(@prompt_entry.value) + end + + stack(width: 0.5) + + button "Accept", width: 0.25 do + if @options[:valid_callback]&.call(@prompt_entry.value) + pop_state + @options[:accept_callback]&.call(@prompt_entry.value) + end + end + end + end + end + + def draw + previous_state&.draw + + Gosu.flush + + super + end + end + end +end diff --git a/media/social_media_icons/discord.png b/media/social_media_icons/discord.png new file mode 100644 index 0000000000000000000000000000000000000000..820ac08618b6f775b1cdfbd785a4cf1428f76473 GIT binary patch literal 3863 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 z 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 O&?x^Pze7H