diff --git a/lib/application_manager.rb b/lib/application_manager.rb index e32e70b..7e5abb6 100644 --- a/lib/application_manager.rb +++ b/lib/application_manager.rb @@ -159,9 +159,34 @@ class W3DHub end end + def mangohud_command(app_id, channel) + return "" if W3DHub.windows? + + # TODO: Add game specific options + # OPENGL? + if false && system("which mangohud") + "MANGOHUD=1 MANGOHUD_DLSYM=1 DXVK_HUD=1 mangohud " + else + "" + end + end + + def dxvk_command(app_id, channel) + return "" if W3DHub.windows? + + # Vulkan + # SETTING && WINE WILL USE DXVK? + if false && true#system() + _setting = "full" + "DXVK_HUD=#{_setting} " + else + "" + end + end + def run(app_id, channel, *args) if (app_data = installed?(app_id, channel)) - pid = Process.spawn("#{wine_command(app_id, channel)}\"#{app_data[:install_path]}\" #{args.join(' ')}") + pid = Process.spawn("#{dxvk_command(app_id, channel)}#{mangohud_command(app_id, channel)}#{wine_command(app_id, channel)}\"#{app_data[:install_path]}\" #{args.join(' ')}") Process.detach(pid) end end @@ -180,7 +205,7 @@ class W3DHub return false unless app_data - server = Store.server_list.select { |server| server.game == app_id && !server.status.password }&.first + server = Store.server_list.select { |server| server.game == app_id && server.channel == channel && !server.status.password }&.first return false unless server diff --git a/lib/pages/games.rb b/lib/pages/games.rb index 80b5d57..1c34908 100644 --- a/lib/pages/games.rb +++ b/lib/pages/games.rb @@ -10,7 +10,7 @@ class W3DHub body.clear do # Games List - @games_list_container = stack(width: 0.15, max_width: 148, height: 1.0, scroll: true) do + @games_list_container = stack(width: 0.15, max_width: 148, height: 1.0, scroll: true, border_thickness_right: 1, border_color_right: 0xff_656565) do end # Game Menu diff --git a/lib/pages/games_redesign.rb b/lib/pages/games_redesign.rb new file mode 100644 index 0000000..6917e7e --- /dev/null +++ b/lib/pages/games_redesign.rb @@ -0,0 +1,300 @@ +class W3DHub + class Pages + class Games < Page + def setup + @game_news ||= {} + @focused_game ||= Store.applications.games.find { |g| g.id == Store.settings[:last_selected_app] } + @focused_game ||= Store.applications.games.find { |g| g.id == "ren" } + @focused_channel ||= @focused_game.channels.find { |c| c.id == Store.settings[:last_selected_channel] } + @focused_channel ||= @focused_game.channels.first + + body.clear do + # Games List + @games_list_container = stack(width: 148, height: 1.0, scroll: true, border_thickness_right: 1, border_color_right: 0xff_656565) do + end + + # Game Menu + @game_page_container = stack(fill: true, height: 1.0) do + end + end + + populate_game_page(@focused_game, @focused_channel) + populate_games_list + end + + def populate_games_list + @games_list_container.clear do + background 0xff_121920 + + Store.applications.games.each do |game| + selected = game == @focused_game + + game_button = stack(width: 1.0, border_thickness_left: 4, + border_color_left: selected ? 0xff_00acff : 0x00_000000, + hover: { background: selected ? game.color : 0xff_444444 }, + padding_top: 4, padding_bottom: 4) do + background game.color if selected + + flow(width: 1.0, height: 48) do + stack(width: 0.3) do + image "#{GAME_ROOT_PATH}/media/ui_icons/return.png", width: 1.0, color: Gosu::Color::GRAY if Store.application_manager.updateable?(game.id, game.channels.first.id) + image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 0.5, color: 0x88_ffffff unless Store.application_manager.installed?(game.id, game.channels.first.id) + end + image_path = File.exist?("#{GAME_ROOT_PATH}/media/icons/#{game.id}.png") ? "#{GAME_ROOT_PATH}/media/icons/#{game.id}.png" : "#{GAME_ROOT_PATH}/media/icons/default_icon.png" + + image image_path, height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff + end + inscription game.name, width: 1.0, text_align: :center + end + + def game_button.hit_element?(x, y) + self if hit?(x, y) + end + + game_button.subscribe(:clicked_left_mouse_button) do + populate_game_page(game, game.channels.first) + populate_games_list + end + end + end + end + + def populate_game_page(game, channel) + @focused_game = game + @focused_channel = channel + + Store.settings[:last_selected_app] = game.id + Store.settings[:last_selected_channel] = channel.id + + @game_page_container.clear do + background game.color + + # Game Stuff + flow(width: 1.0, fill: true) do + # background 0xff_9999ff + + # Game options + stack(width: 360, height: 1.0, padding: 8, scroll: true) do + # background 0xff_550055 + + flow(width: 1.0, height: 200) do + # background 0xff_1155aa + + banner game.name + " [Banner image missing]" + end + + if Store.application_manager.installed?(game.id, channel.id) + Hash.new.tap { |hash| + hash[I18n.t(:"games.game_settings")] = { icon: "gear", block: proc { Store.application_manager.settings(game.id, channel.id) } } + hash[I18n.t(:"games.wine_configuration")] = { icon: "gear", block: proc { Store.application_manager.wine_configuration(game.id, channel.id) } } if W3DHub.unix? + hash[I18n.t(:"games.game_modifications")] = { icon: "gear", enabled: true, block: proc { populate_game_modifications(game, channel) } } + if game.id != "ren" + hash[I18n.t(:"games.repair_installation")] = { icon: "wrench", block: proc { Store.application_manager.repair(game.id, channel.id) } } + hash[I18n.t(:"games.uninstall_game")] = { icon: "trashCan", block: proc { Store.application_manager.uninstall(game.id, channel.id) } } + end + hash[I18n.t(:"games.install_folder")] = { icon: nil, block: proc { Store.application_manager.show_folder(game.id, channel.id, :installation) } } + hash[I18n.t(:"games.user_data_folder")] = { icon: nil, block: proc { Store.application_manager.show_folder(game.id, channel.id, :user_data) } } + hash[I18n.t(:"games.view_screenshots")] = { icon: nil, block: proc { Store.application_manager.show_folder(game.id, channel.id, :screenshots) } } + }.each do |key, hash| + flow(width: 1.0, height: 22, margin_bottom: 8) do + image "#{GAME_ROOT_PATH}/media/ui_icons/#{hash[:icon]}.png", width: 24 if hash[:icon] + image EMPTY_IMAGE, width: 24 unless hash[:icon] + link key, text_size: 18, enabled: hash.key?(:enabled) ? hash[:enabled] : true do + hash[:block]&.call + end + end + end + end + + game.web_links.each do |item| + flow(width: 1.0, height: 22, margin_bottom: 8) do + image "#{GAME_ROOT_PATH}/media/ui_icons/share1.png", width: 24 + link item.name, text_size: 18 do + Launchy.open(item.uri) + end + end + end + + # Spacer + flow(width: 1.0, fill: true) + + # Release channel + flow(width: 1.0, height: 48) do + # background 0xff_444411 + flow(fill: true) + + list_box(width: 1.0, items: game.channels.map(&:name), choose: channel.name, enabled: game.channels.count > 1, margin_left: 24, margin_right: 24) do |value| + populate_game_page(game, game.channels.find { |c| c.name == value }) + end + + flow(fill: true) + end + + # Play buttons + flow(width: 1.0, height: 48, padding_top: 6) do + # background 0xff_551100 + + if Store.application_manager.installed?(game.id, channel.id) + if Store.application_manager.updateable?(game.id, channel.id) + button "#{I18n.t(:"interface.install_update")}", margin_left: 24, **UPDATE_BUTTON do + Store.application_manager.update(game.id, channel.id) + end + else + button "#{I18n.t(:"interface.play_now")}", margin_left: 24 do + Store.application_manager.play_now(game.id, channel.id) + end + end + + button "#{I18n.t(:"interface.single_player")}", margin_left: 24 do + Store.application_manager.run(game.id, channel.id) + end + else + installing = Store.application_manager.task?(:installer, game.id, channel.id) + + unless game.id == "ren" + button "#{I18n.t(:"interface.install")}", margin_left: 24, enabled: !installing do |button| + button.enabled = false + @import_button.enabled = false + Store.application_manager.install(game.id, channel.id) + end + end + + @import_button = button "#{I18n.t(:"interface.import")}", margin_left: 24, enabled: !installing do + Store.application_manager.import(game.id, channel.id) + end + end + end + end + + # Game News + @game_news_container = flow(fill: true, height: 1.0, padding: 8, scroll: true) do + # background 0xff_005500 + end + end + end + + return if Cache.net_lock?("game_news_#{game.id}") + + if @game_news[game.id] + populate_game_news(game) + else + @game_news_container.clear do + title I18n.t(:"games.fetching_news"), padding: 8 + end + + BackgroundWorker.foreground_job( + -> { fetch_game_news(game) }, + lambda do |result| + if result + populate_game_news(game) + Cache.release_net_lock(result) + end + end + ) + end + end + + def fetch_game_news(game) + lock = Cache.acquire_net_lock("game_news_#{game.id}") + return false unless lock + + news = Api.news(game.id) + Cache.release_net_lock("game_news_#{game.id}") unless news + + return false unless news + + news.items[0..15].each do |item| + Cache.fetch(uri: item.image, async: false) + end + + @game_news[game.id] = news + + "game_news_#{game.id}" + end + + def populate_game_news(game) + return unless @focused_game == game + + if (feed = @game_news[game.id]) + @game_news_container.clear do + flow(width: 1.0, height: 150) do + background 0xaa_444400 + + banner "Priority Notification Like Maintenance or Game Night" + end + + feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item| + flow(width: 0.5, max_width: 312, height: 128, margin: 4) do + # background 0x88_000000 + + path = Cache.path(item.image) + + if File.exist?(path) + image path, width: 0.4, padding: 4 + else + image BLACK_IMAGE, width: 0.4, padding: 4 + end + + stack(width: 0.6, height: 1.0) do + stack(width: 1.0, height: 112) do + link "#{item.title}", text_size: 18 do + Launchy.open(item.uri) + end + inscription item.blurb.gsub(/\n+/, "\n").strip[0..180] + end + + flow(width: 1.0) do + inscription item.timestamp.strftime("%Y-%m-%d"), width: 0.5 + link I18n.t(:"games.read_more"), width: 0.5, text_align: :right, text_size: 14 do + Launchy.open(item.uri) + end + end + end + end + end + end + end + end + + def populate_game_modifications(application, channel) + @game_news_container.clear do + ([ + { + id: "4E4CB0548029FF234E289B4B8B3E357A", + name: "HD Purchase Terminal Icons", + author: "username", + description: "Replaces them blurry low res icons with juicy hi-res ones.", + icon: nil, + type: "Textures", + subtype: "Purchase Terminal", + multiplayer_approved: true, + games: ["ren", "ia"], + versions: ["0.0.1", "0.0.2", "0.1.0"], + url: "https://w3dhub.com/mods/username/hd_purchase_terminal_icons" + } + ] * 10).flatten.each do |mod| + flow(width: 1.0, height: 128, margin: 4, border_bottom_thickness: 1, border_bottom_color: 0xff_ffffff) do + stack(width: 128, height: 128, padding: 4) do + image BLACK_IMAGE, height: 1.0 + end + + stack(width: 0.75, height: 1.0) do + stack(width: 1.0, height: 128 - 28) do + link(mod[:name]) { Launchy.open(mod[:url]) } + inscription "Author: #{mod[:author]} | #{mod[:type]} | #{mod[:subtype]}" + para mod[:description][0..180] + end + + flow(width: 1.0, height: 28, padding: 4) do + inscription "Version", width: 0.25, text_align: :center + list_box items: mod[:versions], width: 0.5, enabled: mod[:versions].size > 1, padding_top: 0, padding_bottom: 0 + button "Install", width: 0.25, padding_top: 0, padding_bottom: 0 + end + end + end + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/pages/server_browser.rb b/lib/pages/server_browser.rb index 1b4b380..ee3173f 100644 --- a/lib/pages/server_browser.rb +++ b/lib/pages/server_browser.rb @@ -347,7 +347,7 @@ class W3DHub # Team roster flow(width: 1.0, fill: true, scroll: true) do - stack(width: 0.5) do + stack(width: 0.499) do server.status.players.select { |ply| ply.team == 0 }.sort_by { |ply| ply.score }.reverse.each_with_index do |player, i| flow(width: 1.0, height: 18) do background 0xff_333333 if i.even? diff --git a/lib/states/interface_redesign.rb b/lib/states/interface_redesign.rb new file mode 100644 index 0000000..988f1b4 --- /dev/null +++ b/lib/states/interface_redesign.rb @@ -0,0 +1,207 @@ +class W3DHub + class States + class Interface < CyberarmEngine::GuiState + attr_accessor :interface_task_update_pending + + @@instance = nil + + def self.instance + @@instance + end + + def setup + @@instance = self + + window.show_cursor = true + + @account = @options[:account] + @service_status = @options[:service_status] + @applications = @options[:applications] + + @interface_task_update_pending = nil + + @page = nil + @pages = {} + + Store.application_manager.auto_import + + theme(W3DHub::THEME) + + @interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: 0xff_656565) do + background 0xff_252525 + + @header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: 0xff_656565) do + flow(width: 148, height: 1.0) do + flow(fill: true) + image "#{GAME_ROOT_PATH}/media/icons/app.png", height: 84 + flow(fill: true) + end + + @navigation_container = stack(fill: true, height: 1.0) do + flow(width: 1.0, fill: true) do + # background 0xff_666666 + + link I18n.t(:"interface.games").upcase, text_size: 34 do + page(W3DHub::Pages::Games) + end + + link I18n.t(:"interface.servers").upcase, text_size: 34, margin_left: 12 do + page(W3DHub::Pages::ServerBrowser) + end + + link I18n.t(:"interface.community").upcase, text_size: 34, margin_left: 12 do + page(W3DHub::Pages::Community) + end + + link I18n.t(:"interface.downloads").upcase, text_size: 34, margin_left: 12 do + page(W3DHub::Pages::DownloadManager) + end + + link I18n.t(:"interface.settings").upcase, text_size: 34, margin_left: 12 do + page(W3DHub::Pages::Settings) + end + end + + # Installer task display + flow(width: 1.0, height: 0.5) do + @application_taskbar_container = stack(width: 1.0, height: 1.0, margin_left: 16, margin_right: 16) do + flow(width: 1.0, height: 0.65) do + @application_taskbar_label = inscription "", width: 0.60, text_wrap: :none + @application_taskbar_status_label = inscription "", width: 0.40, text_align: :right, text_wrap: :none + end + + @application_taskbar_progressbar = progress fraction: 0.0, height: 2, width: 1.0 + end + end + end + + @account_container = flow(width: 256, height: 1.0) do + stack(width: 1.0, height: 1.0) do + tagline "#{I18n.t(:"interface.not_logged_in")}", text_wrap: :none + + flow(width: 1.0) do + link(I18n.t(:"interface.log_in"), text_size: 16, width: 0.5) { page(W3DHub::Pages::Login) } + link I18n.t(:"interface.register"), text_size: 16, width: 0.49 do + Launchy.open("https://secure.w3dhub.com/forum/index.php?app=core&module=global§ion=register") + end + end + end + end + end + + @content_container = flow(width: 1.0, fill: true) do + end + end + + if Store.account + page(W3DHub::Pages::Login) + else + page(W3DHub::Pages::Games) + end + + hide_application_taskbar + end + + def draw + super + + @page&.draw + end + + def update + super + + @page&.update + + update_interface_task_status(@interface_task_update_pending) if @interface_task_update_pending + end + + def button_down(id) + super + + @page&.button_down(id) + end + + def button_up(id) + super + + @page&.button_up(id) + end + + def body + @content_container + end + + def page(klass, options = {}) + body.clear + + @page.blur if @page + + @pages[klass] = klass.new(host: self) unless @pages[klass] + @page = @pages[klass] + + @page.options = options + @page.setup + @page.focus + end + + def current_page + @page + end + + def update_server_browser(server) + return unless @page.is_a?(Pages::ServerBrowser) + + @page.refresh_server_list(server) + end + + def show_application_taskbar + @application_taskbar_container.show + end + + def hide_application_taskbar + @application_taskbar_container.hide + end + + def update_interface_task_status(task) + @application_taskbar_label.value = task.status.label + @application_taskbar_status_label.value = "#{task.status.value} (#{format("%.2f%%", task.status.progress.clamp(0.0, 1.0) * 100.0)})" + @application_taskbar_progressbar.value = task.status.progress.clamp(0.0, 1.0) + + return unless @page.is_a?(Pages::DownloadManager) + + operation_info = @page.operation_info + operation_step = @page.operation_info[:___step] + + if task.status.step != operation_step + @page.regenerate(task) + + return + end + + task.status.operations.each do |key, operation| + + name_ = operation_info["#{key}_name"] + status_ = operation_info["#{key}_status"] + progress_ = operation_info["#{key}_progress"] + + next if name_.value == operation.label && + status_.value == operation.value && + progress_.value == operation.value + + name_.value = operation.label if operation.label + status_.value = operation.value if operation.value + + if operation.progress + if operation.progress == Float::INFINITY + progress_.type = :marquee unless progress_.type == :marquee + else + progress_.type = :linear unless progress_.type == :linear + progress_.value = operation.progress.clamp(0.0, 1.0) + end + end + end + end + end + end +end diff --git a/locales/en.yml b/locales/en.yml index 114e1d7..46f10d2 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -12,6 +12,7 @@ en: profile: Profile games: Games server_browser: Server Browser + servers: Servers community: Community download_manager: Download Manager downloads: Downloads @@ -34,6 +35,7 @@ en: read_more: Read More fetching_news: Fetching news... channel: Channel + version: Version server_browser: refresh: Refresh join_server: Join Server diff --git a/w3d_hub_linux_launcher.rb b/w3d_hub_linux_launcher.rb index ad208c5..71ea1ed 100644 --- a/w3d_hub_linux_launcher.rb +++ b/w3d_hub_linux_launcher.rb @@ -76,7 +76,8 @@ require_relative "lib/application_manager/tasks/repairer" require_relative "lib/application_manager/tasks/importer" require_relative "lib/states/demo_input_delay" require_relative "lib/states/boot" -require_relative "lib/states/interface" +# require_relative "lib/states/interface" +require_relative "lib/states/interface_redesign" require_relative "lib/states/welcome" require_relative "lib/states/message_dialog" require_relative "lib/states/prompt_dialog" @@ -92,7 +93,8 @@ require_relative "lib/api/account" require_relative "lib/api/package" require_relative "lib/page" -require_relative "lib/pages/games" +# require_relative "lib/pages/games" +require_relative "lib/pages/games_redesign" require_relative "lib/pages/server_browser" require_relative "lib/pages/community" require_relative "lib/pages/login" @@ -106,7 +108,8 @@ Thread.new do end logger.info(W3DHub::LOG_TAG) { "Launching window..." } -W3DHub::Window.new(width: 980, height: 720, borderless: false, resizable: true).show unless defined?(Ocra) +# W3DHub::Window.new(width: 980, height: 720, borderless: false, resizable: true).show unless defined?(Ocra) +W3DHub::Window.new(width: 1280, height: 800, borderless: false, resizable: true).show unless defined?(Ocra) W3DHub::BackgroundWorker.shutdown! # Wait for BackgroundWorker to return