class W3DHub class States class Interface < CyberarmEngine::GuiState def setup window.show_cursor = true @active_page = nil @focused_game = W3DHub::Game.games.first @main_thread_queue = [] theme({ TextBlock: { text_border: false, text_shadow: true, text_shadow_size: 1, text_shadow_color: 0x88_000000, }, Link: { color: 0xff_cdcdcd, hover: { color: Gosu::Color::WHITE }, active: { color: 0xff_eeeeee } }, Button: { text_size: 18, padding_top: 8, padding_left: 32, padding_right: 32, padding_bottom: 8, border_color: Gosu::Color::NONE, background: 0xff_00acff, hover: { background: 0xff_bee6fd }, active: { background: 0xff_add5ec } } }) @game_news = {} stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: 0xff_aaaaaa) do background 0xff_252525 @header_container = flow(width: 1.0, height: 0.15, padding: 4) do image "#{GAME_ROOT_PATH}/media/icons/w3dhub.png", width: 0.11 stack(width: 0.89, height: 1.0) do # background 0xff_885500 @app_info_container = flow(width: 1.0, height: 0.65) do # background 0xff_8855ff stack(width: 0.75, height: 1.0) do title "W3D Hub Launcher" caption "Version 0.14.0.0", margin_left: 32 end @account_container = flow(width: 0.25, height: 1.0) do # background 0xff_22ff00 stack(width: 0.7, height: 1.0) do # background 0xff_222222 tagline "Cyberarm" flow(width: 1.0) do link("Logout", text_size: 14) { page(:login) } link "Profile", text_size: 14 link("Settings", text_size: 14) { page(:settings) } end end image BLACK_IMAGE, height: 1.0 end end @navigation_container = flow(width: 1.0, height: 0.35) do # background 0xff_666666 flow(width: 0.20, height: 1.0) do end flow(width: 0.55, height: 1.0) do link "Games" do page(:games) end link "Server Browser", margin_left: 18 do page(:server_browser) end link "Community", margin_left: 18 do page(:community) end end flow(width: 0.20, height: 1.0) do end end end end @content_container = flow(width: 1.0, height: 0.85) do end end page(:games) end def update super while(block = @main_thread_queue.shift) block&.call end end def page(page) return if page == @active_page send(:"#{page}_page") @active_page = page end def games_page @content_container.clear do # Games List @games_list_container = stack(width: 0.15, height: 1.0) do end # Game Menu @game_page_container = stack(width: 0.85, height: 1.0) do end end populate_game_page(W3DHub::Game.games.first) populate_games_list end def server_browser_page @content_container.clear do stack(width: 1.0, height: 1.0, padding: 8) do stack(width: 1.0, height: 0.04) do inscription "Filters" end flow(width: 1.0, height: 0.06) do flow(width: 0.75, height: 1.0) do image "#{GAME_ROOT_PATH}/media/icons/ren.png", height: 1.0 do |img| if img.style.color == 0xff_444444 img.style.color = 0xff_ffffff img.style.default[:color] = 0xff_ffffff else img.style.color = 0xff_444444 img.style.default[:color] = 0xff_444444 end end image "#{GAME_ROOT_PATH}/media/icons/ecw.png", height: 1.0, margin_left: 32 do |img| if img.style.color == 0xff_444444 img.style.color = 0xff_ffffff img.style.default[:color] = 0xff_ffffff else img.style.color = 0xff_444444 img.style.default[:color] = 0xff_444444 end end image "#{GAME_ROOT_PATH}/media/icons/ia.png", height: 1.0, margin_left: 32 do |img| if img.style.color == 0xff_444444 img.style.color = 0xff_ffffff img.style.default[:color] = 0xff_ffffff else img.style.color = 0xff_444444 img.style.default[:color] = 0xff_444444 end end image "#{GAME_ROOT_PATH}/media/icons/apb.png", height: 1.0, margin_left: 32 do |img| if img.style.color == 0xff_444444 img.style.color = 0xff_ffffff img.style.default[:color] = 0xff_ffffff else img.style.color = 0xff_444444 img.style.default[:color] = 0xff_444444 end end image "#{GAME_ROOT_PATH}/media/icons/tsr.png", height: 1.0, margin_left: 32, margin_right: 32 do |img| if img.style.color == 0xff_444444 img.style.color = 0xff_ffffff img.style.default[:color] = 0xff_ffffff else img.style.color = 0xff_444444 img.style.default[:color] = 0xff_444444 end end para "Region" list_box items: ["Any", "North America", "Europe"], width: 0.25 end flow(width: 0.249, height: 1.0) do inscription "Nickname:" inscription "Cyberarm" image "#{GAME_ROOT_PATH}/media/ui_icons/wrench.png", height: 16 end end flow(width: 1.0, height: 0.9, margin_top: 16) do stack(width: 0.62, height: 1.0, scroll: true) do # Icon # Hostname # Current Map # Players # Ping flow(width: 1.0, height: 48) do stack(width: 0.08) do end stack(width: 0.50, height: 1.0) do para "Hostname", text_wrap: :none, width: 1.0 end flow(width: 0.24, height: 1.0) do para "Current Map", text_wrap: :none, width: 1.0 end flow(width: 0.11, height: 1.0) do para "Players", text_wrap: :none, width: 1.0 end stack(width: 0.06) do para "Ping", text_wrap: :none, width: 1.0 end end 15.times do |i| flow(width: 1.0, height: 48) do background 0xff_333333 if i.odd? image "#{GAME_ROOT_PATH}/media/icons/ren.png", width: 0.08, padding: 4 stack(width: 0.45, height: 1.0) do inscription "[W3DHub] GAME SERVER" flow(width: 1.0, height: 1.0) do inscription "Release", margin_right: 64, text_size: 14 inscription "North America", text_size: 14 end end flow(width: 0.30, height: 1.0) do inscription "C&C_Vile_Facility_D3.mix" end flow(width: 0.1, height: 1.0) do inscription "127/127" end image "#{GAME_ROOT_PATH}/media/ui_icons/signal3.png", width: 0.05, color: 0xff_008000 end.subscribe(:clicked_left_mouse_button) do populate_server_info(nil) end end end @game_server_info_container = stack(width: 0.38, height: 1.0) do para "No server selected", width: 1.0, text_align: :center end end end end end def community_page @content_container.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." end flow(width: 1.0, height: 0.1, margin_top: 24) do flow(width: 0.375, 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 end end stack(width: 1.0, height: 0.6, 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 end stack(width: 1.0, height: 0.15) 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/") } para "on the W3D Hub Discord server" end end end end end def login_page @content_container.clear do stack(width: 1.0, height: 1.0, padding: 32) do background 0xff_252535 para "Login using your W3D Hub forum account" flow(width: 1.0) do tagline "Username", width: 0.25, text_align: :right, focus: true edit_line "" end flow(width: 1.0) do tagline "Password", width: 0.25, text_align: :right edit_line "", type: :password end flow(width: 1.0) do tagline "", width: 0.25 button "Log In" end end end end def settings_page @content_container.clear do stack(width: 1.0, height: 1.0, padding: 64) do para "Language" para "Select the UI language you'd like to use in the W3D Hub Launcher. You should restart the launcher after changing this setting before the ui will update", width: 1.0 list_box items: ["English", "French", "German"], width: 1.0 para "Folder Paths", margin_top: 32 stack(width: 1.0, height: 0.35) do flow(width: 1.0, height: 0.5) do para "App Install Folder", width: 0.249 stack(width: 0.75, height: 1.0) do edit_line "C:\\Program Files (x86)\\W3D Hub", width: 1.0 inscription "The folder into which new games and apps will be installed by the launcher" end end flow(width: 1.0, height: 0.5) do para "Package Cache Folder", width: 0.249 stack(width: 0.75, height: 1.0) do edit_line "C:\\Program Data\\W3D Hub\\Launcher\\package-cache", width: 1.0 inscription "A folder which will be used to cache downloaded packages used to install games and apps" end end end para "Diagnostics" check_box "Enable Automatic Error Reporting", text_size: 16 inscription "If this is enabled the launcher will automatically report errors to the development team, along with basic information about your machine, such as operating system.", width: 1.0 button "Save", margin_top: 32 end end end def populate_games_list @games_list_container.clear do background 0xff_121920 W3DHub::Game.games.each do |game| selected = game == @focused_game stack(width: 1.0, border_thickness_left: 4, border_color_left: selected ? 0xff_00acff : 0x00_000000) do background game.background_color if selected image game.icon, height: 48 inscription game.name end.subscribe(:clicked_left_mouse_button) do |e| populate_game_page(game) populate_games_list end end end end def populate_game_page(game) @focused_game = game @game_page_container.clear do background game.background_color # Release channel flow(width: 1.0, height: 0.03) do # background 0xff_444411 inscription "Release" end # Game Stuff flow(width: 1.0, height: 0.89) do # background 0xff_9999ff # Gane options stack(width: 0.25, height: 1.0, padding: 8) do # background 0xff_550055 game.menu_items.each do |item| flow(width: 1.0, height: 22, margin_bottom: 8) do image item.image, width: 0.11 link item.label, text_size: 18 end end end # Game News @game_news_container = flow(width: 0.75, height: 1.0, padding: 8, scroll: true) do # background 0xff_005500 end end # Play buttons flow(width: 1.0, height: 0.08) do # background 0xff_551100 game.play_items.each do |item| button "#{item.label}", margin_left: 24 do item.block&.call(game) end end end end unless @game_news[game.slot] Thread.new do fetch_game_news(game) @main_thread_queue << proc { populate_game_news(game) } end @game_news_container.clear do title "Fetching News...", padding: 8 end else populate_game_news(game) end end # FIXME: Do actual gui update on main thread def fetch_game_news(game) feed_uri = Excon.get( game.news_feed, headers: { "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0", "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Encoding" => "deflate", "Accept-Language" => "en-US,en;q=0.5", "Host" => "w3dhub.com", "DNT" => "1" } ) @game_news[game.slot] = RSS::Parser.parse(feed_uri.body) if feed_uri.status == 200 end def populate_game_news(game) return unless @focused_game == game if (feed = @game_news[game.slot]) @game_news_container.clear do feed.items.sort_by { |i| i.pubDate }.reverse[0..9].each do |item| flow(width: 0.5, height: 128, margin: 4) do # background 0x88_000000 image game.icon, width: 0.4 stack(width: 0.6, height: 1.0) do stack(width: 1.0, height: 112) do para "#{item.title}" inscription "#{Sanitize.fragment(item.description[0...180]).strip}" end flow(width: 1.0) do inscription item.pubDate.strftime("%Y-%m-%d"), width: 0.5 link "Read More", width: 0.5, text_align: :right, text_size: 14 do Launchy.open(item.link) end end end end end end end end def populate_server_info(server) @game_server_info_container.clear do stack(width: 1.0, height: 1.0, padding: 8) do stack(width: 1.0, height: 0.3) do flow(width: 1.0, height: 0.2) do image "#{GAME_ROOT_PATH}/media/icons/ia.png", height: 24 tagline "[W3D Hub] GAME SERVER" end stack(width: 1.0, height: 0.25) do button "Join Server" end stack(width: 1.0, height: 0.55, margin_top: 16) do flow(width: 1.0, height: 0.33) do inscription "Game", width: 0.4 inscription "GAME (branch)", width: 0.6 end flow(width: 1.0, height: 0.33) do inscription "Map", width: 0.4 inscription "C&C_Islands.mix", width: 0.6 end flow(width: 1.0, height: 0.33) do inscription "Max Players", width: 0.4 inscription "127", width: 0.6 end end end flow(width: 1.0, height: 0.05) do stack(width: 0.5, height: 1.0) do para "GDI", width: 1.0, text_align: :center end stack(width: 0.5, height: 1.0) do para "Nod", width: 1.0, text_align: :center end end flow(width: 1.0, height: 0.65) do stack(width: 0.5, height: 1.0, scroll: true) do 15.times do |i| flow(width: 1.0, height: 18) do stack(width: 0.6, height: 1.0) do inscription "Player Name #{i}", text_size: 14 end stack(width: 0.4, height: 1.0) do inscription "#{rand(1000..100000)}", text_size: 14, width: 1.0, text_align: :right end end end end stack(width: 0.5, height: 1.0, scroll: true, border_thickness_left: 2, border_color_left: 0xff_000000) do 45.times do |i| flow(width: 1.0, height: 18) do stack(width: 0.6, height: 1.0) do inscription "Player Name #{i}", text_size: 14 end stack(width: 0.4, height: 1.0) do inscription "#{rand(1000..100000)}", text_size: 14, width: 1.0, text_align: :right end end end end end end end end end end end