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