diff --git a/Gemfile b/Gemfile index 9bd9f6d..ae4dc7c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source "https://rubygems.org" -gem "cyberarm_engine" \ No newline at end of file +gem "cyberarm_engine" +gem "sanitize" +gem "rss" +gem "launchy" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 815a033..2ffa7b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,10 @@ GEM remote: https://rubygems.org/ specs: + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) clipboard (1.3.6) + crass (1.0.6) cyberarm_engine (0.19.1) clipboard (~> 1.3.5) excon (~> 0.78.0) @@ -10,12 +13,30 @@ GEM excon (0.78.1) gosu (1.2.0) gosu_more_drawables (0.3.1) + launchy (2.5.0) + addressable (~> 2.7) + nokogiri (1.11.7-x64-mingw32) + racc (~> 1.4) + nokogumbo (2.0.5) + nokogiri (~> 1.8, >= 1.8.4) + public_suffix (4.0.6) + racc (1.5.2) + rexml (3.2.5) + rss (0.2.9) + rexml + sanitize (5.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.8.0) + nokogumbo (~> 2.0) PLATFORMS x64-mingw32 DEPENDENCIES cyberarm_engine + launchy + rss + sanitize BUNDLED WITH 2.2.28 diff --git a/lib/game.rb b/lib/game.rb new file mode 100644 index 0000000..977b9d8 --- /dev/null +++ b/lib/game.rb @@ -0,0 +1,76 @@ +class W3DHub + class Game + include CyberarmEngine::Common + + MenuItem = Struct.new(:image, :label, :block) + PlayItem = Struct.new(:label, :block) + + @@games = [] + @@subclasses = [] + + attr_reader :slot, :name, :icon, :news_feed, :background_color, :menu_items, :play_items + + def self.inherited(klass) + super + + @@subclasses << klass + end + + def self.load_games + @@subclasses.each do |klass| + i = klass.new + i.setup + + @@games << i + end + + @@games.sort! { |g| g.slot } + end + + def self.games + @@games + end + + def initialize + @slot = -1 + + @name = "???" + @icon = EMPTY_IMAGE + @news_feed = "???" + @background_color = 0xff_ffffff + + @menu_items = [] + @play_items = [] + end + + def set_slot(index) + @slot = index + end + + def set_name(name) + @name = name + end + + def set_icon(path_or_image) + @icon = path_or_image.is_a?(Gosu::Image) ? path_or_image : path_or_image.nil? ? EMPTY_IMAGE : get_image(path_or_image) + end + + def set_news_feed(uri) + @news_feed = uri + end + + def set_background_color(color) + @background_color = color + end + + def menu_item(path_or_image, label, &block) + image = path_or_image.is_a?(Gosu::Image) ? path_or_image : path_or_image.nil? ? EMPTY_IMAGE : get_image(path_or_image) + + @menu_items << MenuItem.new(image, label, block) + end + + def play_item(label, &block) + @play_items << PlayItem.new(label, block) + end + end +end diff --git a/lib/games/expansive_civilian_warfare.rb b/lib/games/expansive_civilian_warfare.rb new file mode 100644 index 0000000..0116ed8 --- /dev/null +++ b/lib/games/expansive_civilian_warfare.rb @@ -0,0 +1,26 @@ +class W3DHub + class Games + class ExpansiveCivilianWarfare < Game + def setup + set_name "Expansive Civilian Warfare" + set_icon "#{GAME_ROOT_PATH}/media/icons/ecw.png" + set_news_feed "https://w3dhub.com/forum/forum/208-expansive-civilian-warfare.xml" + set_background_color 0xff_3e5c87 + + menu_item(nil, "Game Settings") + menu_item(nil, "Repair Installation") + menu_item(nil, "Uninstall") + + menu_item(nil, "Install Folder") + menu_item(nil, "User Data Folder") + menu_item(nil, "View Screenshots") + menu_item(nil, "Player Statistics") + + play_item("Play Game") + play_item("Single Player") + + set_slot(1) + end + end + end +end diff --git a/lib/games/interim_apex.rb b/lib/games/interim_apex.rb new file mode 100644 index 0000000..8118e59 --- /dev/null +++ b/lib/games/interim_apex.rb @@ -0,0 +1,25 @@ +class W3DHub + class Games + class InterimApex < Game + def setup + set_name "Interim Apex" + set_icon "#{GAME_ROOT_PATH}/media/icons/ia.png" + set_news_feed "https://w3dhub.com/forum/forum/209-interim-apex.xml" + set_background_color 0xff_034866 + + menu_item(nil, "Game Settings") + menu_item(nil, "Repair Installation") + menu_item(nil, "Uninstall") + + menu_item(nil, "Install Folder") + menu_item(nil, "User Data Folder") + menu_item(nil, "View Screenshots") + + play_item("Play Game") + play_item("Single Player") + + set_slot(2) + end + end + end +end diff --git a/lib/games/ra_a_path_beyond.rb b/lib/games/ra_a_path_beyond.rb new file mode 100644 index 0000000..66fea9b --- /dev/null +++ b/lib/games/ra_a_path_beyond.rb @@ -0,0 +1,28 @@ +class W3DHub + class Games + class APathBeyond < Game + def setup + set_name "Red Alert: A Path Beyond" + set_icon "#{GAME_ROOT_PATH}/media/icons/apb.png" + set_news_feed "https://w3dhub.com/forum/forum/201-red-alert-a-path-beyond.xml" + set_background_color 0xff_353535 + + menu_item(nil, "Game Settings") + menu_item(nil, "Repair Installation") + menu_item(nil, "Uninstall") + + menu_item(nil, "Install Folder") + menu_item(nil, "User Data Folder") + menu_item(nil, "View Screenshots") + menu_item(nil, "Modifications") + menu_item(nil, "Bug Tracker") + menu_item(nil, "Player Statistics") + + play_item("Play Game") + play_item("Single Player") + + set_slot(3) + end + end + end +end diff --git a/lib/games/renegade.rb b/lib/games/renegade.rb new file mode 100644 index 0000000..730b517 --- /dev/null +++ b/lib/games/renegade.rb @@ -0,0 +1,24 @@ +class W3DHub + class Games + class CNCRenegade < Game + def setup + set_name "C&C Renegade" + set_icon "#{GAME_ROOT_PATH}/media/icons/ren.png" + set_news_feed "https://w3dhub.com/forum/forum/231-command-and-conquer-renegade.xml" + set_background_color 0xff_b03f25 + + menu_item(nil, "Game Settings") + + menu_item(nil, "Install Folder") + menu_item(nil, "View Screenshots") + menu_item(nil, "> GET SCRIPTS 4.7 <") + menu_item(nil, "Renegade News") + + play_item("Play Game") + play_item("Single Player") + + set_slot(0) + end + end + end +end diff --git a/lib/games/ts_reborn.rb b/lib/games/ts_reborn.rb new file mode 100644 index 0000000..273914c --- /dev/null +++ b/lib/games/ts_reborn.rb @@ -0,0 +1,27 @@ +class W3DHub + class Games + class TSReborn < Game + def setup + set_name "Tiberian Sun: Reborn" + set_icon "#{GAME_ROOT_PATH}/media/icons/tsr.png" + set_news_feed "https://w3dhub.com/forum/forum/97-tiberian-sun-reborn.xml" + set_background_color 0xff_497331 + + menu_item(nil, "Game Settings") + menu_item(nil, "Repair Installation") + menu_item(nil, "Uninstall") + + menu_item(nil, "Install Folder") + menu_item(nil, "User Data Folder") + menu_item(nil, "View Screenshots") + menu_item(nil, "Discord") + menu_item(nil, "Modifications") + + play_item("Play Game") + play_item("Single Player") + + set_slot(4) + end + end + end +end diff --git a/lib/states/boot.rb b/lib/states/boot.rb index e69de29..7ef6c7a 100644 --- a/lib/states/boot.rb +++ b/lib/states/boot.rb @@ -0,0 +1,40 @@ +class W3DHub + class States + class Boot < CyberarmEngine::GuiState + def setup + background 0xff_252525 + + @fraction = 0.0 + @w3dhub_logo = get_image("#{GAME_ROOT_PATH}/media/icons/w3dhub.png") + + stack(width: 1.0, height: 1.0) do + stack(width: 1.0, height: 0.925) do + end + + @progressbar = progress height: 0.025, width: 1.0 + + flow(width: 1.0, height: 0.05, padding_left: 16, padding_right: 16, padding_bottom: 8, padding_top: 8) do + caption "Checking for updates...", width: 0.5 + inscription "W3D Hub Launcher 0.14.0.0", width: 0.5, text_align: :right + end + end + end + + def draw + @w3dhub_logo.draw_rot(window.width / 2, window.height / 2, 32) + + super + end + + def update + super + + @fraction += 1.0 * window.dt + + @progressbar.value = @fraction + + push_state(States::Interface) if @progressbar.value >= 1.0 + end + end + end +end diff --git a/lib/states/interface.rb b/lib/states/interface.rb index 1947dce..7e1ea6e 100644 --- a/lib/states/interface.rb +++ b/lib/states/interface.rb @@ -4,29 +4,64 @@ class W3DHub def setup window.show_cursor = true + 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) do @header_container = flow(width: 1.0, height: 0.15, padding: 4) do - background 0xff_888888 + background 0xff_252525 image "#{GAME_ROOT_PATH}/media/icons/w3dhub.png", width: 0.11 stack(width: 0.89, height: 1.0) do - background 0xff_885500 + # background 0xff_885500 @app_info_container = flow(width: 1.0, height: 0.65) do - background 0xff_8855ff + # background 0xff_8855ff stack(width: 0.75, height: 1.0) do - title "W3D Hub Launcher" - caption "Version 0.13.0.4", margin_left: 32 + 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 + # background 0xff_22ff00 stack(width: 0.7, height: 1.0) do - background 0xff_222222 - tagline "Cyberarm" + # background 0xff_222222 + tagline "Cyberarm" flow(width: 1.0) do link "Logout", text_size: 14 @@ -34,113 +69,156 @@ class W3DHub end end - image "#{GAME_ROOT_PATH}/media/icons/ia.png", height: 1.0 + image EMPTY_IMAGE, height: 1.0 end end @navigation_container = flow(width: 1.0, height: 0.35) do - background 0xff_666666 + # background 0xff_666666 - flow(width: 0.25, height: 1.0) do + flow(width: 0.20, height: 1.0) do end - flow(width: 0.5, height: 1.0) do - link "Games" - link "Server Browser" - link "Community" + flow(width: 0.55, height: 1.0) do + link "Games" + link "Server Browser", margin_left: 18 + link "Community", margin_left: 18 end - flow(width: 0.25, height: 1.0) do + flow(width: 0.20, height: 1.0) do end end end end @content_container = flow(width: 1.0, height: 0.85) do - background 0xff_44aa00 + # background 0xff_44aa00 # Games List stack(width: 0.15, height: 1.0) do - background 0xff_559900 + background 0xff_121920 - stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do - background 0xff_663300 + W3DHub::Game.games.each do |game| + stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do + background game.background_color - image "#{GAME_ROOT_PATH}/media/icons/ren.png", height: 48 - inscription "C&C Renegade" - end.subscribe(:clicked_left_mouse_button) do |e| - puts "CLICKED" - end - - stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do - background 0xff_4444ff - - image "#{GAME_ROOT_PATH}/media/icons/ecw.png", height: 48 - inscription "Exspansive Civilian Warfare" - end.subscribe(:clicked_left_mouse_button) do |e| - puts "CLICKED" - end - - stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do - background 0xff_444488 - - image "#{GAME_ROOT_PATH}/media/icons/ia.png", height: 48 - inscription "Interim Apex" - end.subscribe(:clicked_left_mouse_button) do |e| - puts "CLICKED" - end - - stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do - background 0xff_444444 - - image "#{GAME_ROOT_PATH}/media/icons/apb.png", height: 48 - inscription "Red Alert: A Path Beyond" - end.subscribe(:clicked_left_mouse_button) do |e| - puts "CLICKED" - end - - stack(width: 1.0, border_thickness_left: 4, border_color_left: 0xff_000000) do - background 0xff_448844 - - image "#{GAME_ROOT_PATH}/media/icons/tsr.png", height: 48 - inscription "Tiberium Sun: Reborn" - end.subscribe(:clicked_left_mouse_button) do |e| - puts "CLICKED" + image game.icon, height: 48 + inscription game.name + end.subscribe(:clicked_left_mouse_button) do |e| + populate_game_page(game) + end end end # Game Menu - stack(width: 0.85, height: 1.0) do - background 0xff_5511ff + @game_page_container = stack(width: 0.85, height: 1.0) do + end + end + end - # Release channel - flow(width: 1.0, height: 0.03) do - background 0xff_444411 + populate_game_page(W3DHub::Game.games.first) + end - inscription "Release" - end + def populate_game_page(game) + @focused_game = game - # Game Stuff - flow(width: 1.0, height: 0.89) do - background 0xff_9999ff + @game_page_container.clear do + background game.background_color - # Gane options - stack(width: 0.25, height: 1.0) do - background 0xff_550055 - end + # Release channel + flow(width: 1.0, height: 0.03) do + # background 0xff_444411 - # Game News - flow(width: 0.75, height: 1.0) do - background 0xff_005500 + 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 - # Play buttons - flow(width: 1.0, height: 0.08) do - background 0xff_551100 + # Game News + @game_news_container = flow(width: 0.75, height: 1.0, padding: 8, scroll: true) do + # background 0xff_005500 + end + end - button "Play Now" - button "Single player" + # 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) + 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) + 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_ROOT_PATH}/media/icons/ia.png", 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 diff --git a/lib/window.rb b/lib/window.rb index 866caf2..b53ea54 100644 --- a/lib/window.rb +++ b/lib/window.rb @@ -3,8 +3,7 @@ class W3DHub def setup self.caption = "W3D Hub Launcher" - # push_state(W3DHub::States::Boot) - push_state(W3DHub::States::Interface) + push_state(W3DHub::States::Boot) end end end diff --git a/w3dhub.rb b/w3dhub.rb index dc3f51b..43fdf56 100644 --- a/w3dhub.rb +++ b/w3dhub.rb @@ -1,9 +1,23 @@ require "cyberarm_engine" +require "sanitize" +require "rss" +require "zlib" +require "launchy" GAME_ROOT_PATH = File.expand_path(".", __dir__) +EMPTY_IMAGE = Gosu::Image.from_blob(1, 1) require_relative "lib/window" require_relative "lib/states/boot" require_relative "lib/states/interface" +require_relative "lib/game" +require_relative "lib/games/renegade" +require_relative "lib/games/expansive_civilian_warfare" +require_relative "lib/games/interim_apex" +require_relative "lib/games/ra_a_path_beyond" +require_relative "lib/games/ts_reborn" + +W3DHub::Game.load_games + W3DHub::Window.new(width: 980, height: 720, borderless: false).show