Compare commits

...

3 Commits

20 changed files with 1344 additions and 1168 deletions

View File

@@ -1,6 +1,7 @@
source "https://rubygems.org"
gem "cyberarm_engine"
gem "sdl2-bindings"
gem "digest-crc"
gem "i18n"
gem "ircparser"

View File

@@ -2,27 +2,29 @@ GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (1.2.2)
cyberarm_engine (0.23.0)
cyberarm_engine (0.24.0)
excon (~> 0.88)
gosu (~> 1.1)
gosu_more_drawables (~> 0.3)
digest-crc (0.6.4)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
event_emitter (0.2.6)
excon (0.99.0)
ffi (1.15.5)
excon (0.104.0)
ffi (1.16.3)
ffi-win32-extensions (1.0.4)
ffi
gosu (1.4.6)
gosu_more_drawables (0.3.1)
i18n (1.13.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
ircparser (1.0.0)
rake (13.0.6)
rexml (3.2.5)
rake (13.1.0)
rexml (3.2.6)
rubyzip (2.3.2)
websocket (1.2.9)
websocket-client-simple (0.6.1)
sdl2-bindings (0.2.2)
ffi (~> 1.15)
websocket (1.2.10)
websocket-client-simple (0.8.0)
event_emitter
websocket
win32-process (0.10.0)
@@ -43,9 +45,10 @@ DEPENDENCIES
ircparser
rexml
rubyzip
sdl2-bindings
websocket-client-simple
win32-process
win32-security
BUNDLED WITH
2.4.13
2.4.14

View File

@@ -448,6 +448,14 @@ class W3DHub
Store.settings.save_settings
end
def color(app_id)
Store.applications.games.detect { |g| g.id == app_id }&.color
end
def name(app_id)
Store.applications.games.detect { |g| g.id == app_id }&.name
end
# No application tasks are being done
def idle?
!busy?

View File

@@ -39,7 +39,7 @@ class W3DHub
path = Cache.install_path(@application, @channel)
log path
logger.info(LOG_TAG) { path }
# TODO: Do some sanity checking, i.e. DO NOT start launcher if `whoami` returns root, path makes sense,
# we're not on Windows trying to uninstall a game likely installed by the official launcher
FileUtils.remove_dir(path)
@@ -55,7 +55,7 @@ class W3DHub
@status.step = :mark_application_uninstalled
log "#{@app_id} has been uninstalled."
logger.info(LOG_TAG) { "#{@app_id} has been uninstalled." }
end
end
end

271
lib/game_settings.rb Normal file
View File

@@ -0,0 +1,271 @@
class W3DHub
class GameSettings
TYPE_LIBCONFIG = 0
TYPE_REGISTRY = 1
Setting = Struct.new(:group, :name, :label, :type, :key, :value, :options, :indexed)
def initialize(app_id, channel)
@app_id = app_id
@channel = channel
# Minimium width/height to show in options
@min_width = 1280
@min_height = 720
@win32_registry_base = "SOFTWARE\\W3D Hub\\games\\#{app_id}-#{channel}".freeze
@engine_cfg_path = "#{Dir.home}/Documents/W3D Hub/games/#{app_id}-#{channel}/engine.cfg".freeze
@hardware_data = HardwareSurvey.new.data
@cfg = File.exist?(@engine_cfg_path) ? File.read(@engine_cfg_path) : nil
@cfg_hash = {}
resolutions = @hardware_data[:displays].map { |display| display[:resolutions] }.flatten.each_slice(2).select do |pair|
width = pair[0]
height = pair[1]
width >= @min_width && height >= @min_height && width > height
end
refresh_rates = ([300, 240, 165, 144, 120, 75, 60, 59, 50, 40] + @hardware_data[:displays].map do |display|
display[:refresh_rates]
end).flatten.uniq.sort.reverse.map { |r| [r, r] }
@settings = {}
# General
@settings[:default_to_first_person] = Setting.new(:general, :default_to_first_person, "Default to First Person", TYPE_REGISTRY, "Options\\DefaultToFirstPerson", true)
@settings[:background_downloads] = Setting.new(:general, :background_downloads, "Background Downloads", TYPE_REGISTRY, "BackgroundDownloadingEnabled", true)
@settings[:hints_enabled] = Setting.new(:general, :hints_enabled, "Enable Hints", TYPE_REGISTRY, "HintsEnabled", true)
@settings[:chat_log] = Setting.new(:general, :chat_log, "Enable Chat Log", TYPE_REGISTRY, "ClientChatLog", true)
@settings[:show_fps] = Setting.new(:general, :show_fps, "Show FPS", TYPE_REGISTRY, "Networking\\Debug\\ShowFps", true)
@settings[:show_velocity] = Setting.new(:general, :show_velocity, "Show Velocity", TYPE_REGISTRY, "ShowVelocity", true)
@settings[:show_damage_numbers] = Setting.new(:general, :show_damage_numbers, "Show Damage Numbers", TYPE_REGISTRY, "Options\\HitDamageOnScreen", true)
# Audio
@settings[:master_volume] = Setting.new(:audio, :master_volume, "Master Volume", TYPE_REGISTRY, "Sound\\master volume", 1.0)
@settings[:master_enabled] = Setting.new(:audio, :master_enabled, "Master Volume Enabled", TYPE_REGISTRY, "Sound\\master enabled", true)
@settings[:sound_effects_volume] = Setting.new(:audio, :sound_effects_volume, "Sound Effects", TYPE_REGISTRY, "Sound\\sound volume", 0.40)
@settings[:sound_effects_enabled] = Setting.new(:audio, :sound_effects_enabled, "Sound Effects Enabled", TYPE_REGISTRY, "Sound\\sound enabled", true)
@settings[:sound_dialog_volume] = Setting.new(:audio, :sound_dialog_volume, "Dialog", TYPE_REGISTRY, "Sound\\dialog volume", 0.75)
@settings[:sound_dialog_enabled] = Setting.new(:audio, :sound_dialog_enabled, "Dialog Enabled", TYPE_REGISTRY, "Sound\\dialog enabled", true)
@settings[:sound_music_volume] = Setting.new(:audio, :sound_music_volume, "Music", TYPE_REGISTRY, "Sound\\music volume", 0.75)
@settings[:sound_music_enabled] = Setting.new(:audio, :sound_music_enabled, "Music Enabled", TYPE_REGISTRY, "Sound\\music enabled", true)
@settings[:sound_cinematic_volume] = Setting.new(:audio, :sound_cinematic_volume, "Cinematic", TYPE_REGISTRY, "Sound\\cinematic volume", 0.75)
@settings[:sound_cinematic_enabled] = Setting.new(:audio, :sound_cinematic_enabled, "Cinematic Enabled", TYPE_REGISTRY, "Sound\\cinematic enabled", true)
@settings[:sound_in_background] = Setting.new(:audio, :sound_in_background, "Play Sound with Game in Background", TYPE_REGISTRY, "Sound\\mute in background", false)
# Video
@settings[:resolution_width] = Setting.new(:video, :resolution_width, "Resolution", TYPE_LIBCONFIG, "Render:Width", resolutions.first[0], resolutions.map { |a| [a[0], a[0]] })
@settings[:resolution_height] = Setting.new(:video, :resolution_height, "Resolution", TYPE_LIBCONFIG, "Render:Height", resolutions.first[1], resolutions.map { |a| [a[1], a[1]] })
@settings[:windowed_mode] = Setting.new(:video, :windowed_mode, "Windowed Mode", TYPE_LIBCONFIG, "Render:FullscreenMode", 2, [["Windowed", 0], ["Fullscreen", 1], ["Borderless", 2]], true)
@settings[:vsync] = Setting.new(:video, :vsync, "Enable VSync", TYPE_LIBCONFIG, "Render:DisableVSync", true)
@settings[:fps] = Setting.new(:video, :fps, "FPS Limit", TYPE_LIBCONFIG, "Render:MaxFPS", refresh_rates.first[1], refresh_rates)
@settings[:anti_aliasing] = Setting.new(:video, :anti_aliasing, "Anti-aliasing", TYPE_REGISTRY, "System Settings\\Antialiasing_Mode", 0x80000001, [["None", 0], ["2x", 0x80000000], ["4x", 0x80000001], ["8x", 0x80000002]], true)
# Performance
@settings[:texture_detail] = Setting.new(:performance, :texture_detail, "Texture Detail", TYPE_REGISTRY, "System Settings\\Texture_Resolution", 0, [["High",0], ["Medium", 1], ["Low", 2]], true)
@settings[:texture_filtering] = Setting.new(:performance, :texture_filtering, "Texture Filtering", TYPE_REGISTRY, "System Settings\\Texture_Filter_Mode", 3, [["Bilinear", 0], ["Trilinear", 1], ["Anisotropic 2x", 2], ["Anisotropic 4x", 3], ["Anisotropic 8x", 4], ["Anisotropic 16x", 5]], true)
@settings[:shadow_resolution] = Setting.new(:performance, :shadow_resolution, "Shadow Resolution", TYPE_REGISTRY, "System Settings\\Dynamic_Shadow_Resolution", 512, [["128", 128], ["256", 256], ["512", 512], ["1024", 1024], ["2048*", 2048], ["4096*", 4096]], true)
@settings[:high_quality_shadows] = Setting.new(:general, :high_quality_shadows, "High Quality Shadows", TYPE_REGISTRY, "HighQualityShadows", true)
load_settings
end
def get(key)
@settings[key]
end
def get_value(key)
setting = get(key)
if setting.options.is_a?(Array) && setting.indexed
setting.options[setting.options.map(&:last).index(setting.value)][0]
else
setting.value
end
end
def set_value(key, value)
setting = get(key)
if setting.options.is_a?(Array)
setting.value = setting.options.find { |v| v[0] == value }[1]
elsif setting.options.is_a?(Hash)
setting.value = value.clamp(setting.options[:min], setting.options[:max])
else
setting.value = value
end
end
def load_settings
load_from_registry
load_from_cfg
end
def load_from_registry
@settings.each do |_key, setting|
next unless setting.type == TYPE_REGISTRY
data = nil
begin
data = read_reg(setting.key)
rescue Win32::Registry::Error
end
next unless data
if setting.value.is_a?(TrueClass) || setting.value.is_a?(FalseClass)
setting.value = data == 1
elsif setting.value.is_a?(Float)
if setting.group == :audio
setting.value = data.to_f / 100.0
else
setting.value = data
end
elsif setting.value.is_a?(Integer)
setting.value = data
else
raise "UNKNOWN VALUE TYPE: #{setting.value.class}"
end
end
end
def load_from_cfg
@cfg_hash = {}
if @cfg
in_hash = false
@cfg.lines.each do |line|
line = line.strip
break if line.start_with?("}")
if line.start_with?("{")
in_hash = true
next
end
next unless in_hash
parts = line.split("=").map { |l| l.strip.sub(";", "")}
@cfg_hash[parts.first] = parts.last
end
end
@cfg_hash.each do |key, value|
next if value.start_with?("\"")
begin
@cfg_hash[key] = Integer(value)
rescue ArgumentError # Not an int
@cfg_hash[key] = value == "true" ? true : false if value == "true" || value == "false"
@cfg_hash[key] = !@cfg_hash[key] if key == "DisableVSync" # UI shows enable vsync, cfg stores disable vsync
end
end
@settings.each do |key, setting|
next unless setting.type == TYPE_LIBCONFIG
cfg_key = setting.key.split(":").last
v = @cfg_hash[cfg_key]
if v != nil
if v.is_a?(TrueClass) || v.is_a?(FalseClass)
setting.value = v
elsif v.is_a?(Integer)
i = setting.options.map(&:last).index(v) || 0
if ["Width", "Height"].include?(cfg_key)
set_value(key, setting.options[i][0])
elsif cfg_key == "MaxFPS"
setting.value = v
end
end
else
@cfg_hash[cfg_key] = setting.value
end
end
end
def save_settings!
save_to_registry!
save_to_cfg!
end
def save_to_registry!
@settings.each do |_key, setting|
next unless setting.type == TYPE_REGISTRY
if setting.value.is_a?(TrueClass) || setting.value.is_a?(FalseClass)
write_reg(setting.key, setting.value ? 1 : 0)
elsif setting.value.is_a?(Float)
if setting.group == :audio
write_reg(setting.key, (setting.value * 100.0).round.clamp(0, 100))
else
write_reg(setting.key, setting.value)
end
elsif setting.value.is_a?(Integer)
write_reg(setting.key, setting.value)
else
raise "UNKNOWN VALUE TYPE: #{setting.value.class}"
end
end
end
def save_to_cfg!
@settings.each do |key, setting|
next unless setting.type == TYPE_LIBCONFIG
cfg_key = setting.key.split(":").last
v = @cfg_hash[cfg_key]
if v
# UI shows enable vsync, cfg stores disable vsync
@cfg_hash[cfg_key] = cfg_key == "DisableVSync" ? !setting.value : setting.value
end
end
string = "Render : \n{\n"
@cfg_hash.each do |key, value|
string += " #{key} = #{value.to_s};\n"
end
string += "};\n"
FileUtils.mkdir_p(File.dirname(@engine_cfg_path)) unless Dir.exist?(File.dirname(@engine_cfg_path))
File.write(@engine_cfg_path, string)
end
def read_reg(key)
keys = key.split("\\")
sub_key = keys.size > 1 ? keys[0..(keys.size - 2)].join("\\") : ""
target_key = keys.last
reg_key = "#{@win32_registry_base}\\#{sub_key}".freeze
value = nil
Win32::Registry::HKEY_CURRENT_USER.open(reg_key) do |reg|
value = reg[target_key]
end
value
end
def write_reg(key, value)
keys = key.split("\\")
sub_key = keys.size > 1 ? keys[0..(keys.size - 2)].join("\\") : ""
target_key = keys.last
reg_key = "#{@win32_registry_base}#{sub_key.empty? ? '' : "\\#{sub_key}"}".freeze
begin
Win32::Registry::HKEY_CURRENT_USER.open(reg_key, Win32::Registry::KEY_WRITE) do |reg|
reg[target_key] = value
end
rescue Win32::Registry::Error
result = Win32::Registry::HKEY_CURRENT_USER.create(reg_key)
result.write_i(target_key, value)
end
end
end
end

189
lib/hardware_survey.rb Normal file
View File

@@ -0,0 +1,189 @@
class W3DHub
class HardwareSurvey
attr_reader :data
def initialize
@data = {
displays: [],
system: {
motherboard: {
manufacturer: "Unknown",
model: "Unknown",
bios_vendor: "Unknown",
bios_release_date: "Unknown",
bios_version: "Unknown"
},
operating_system: {
name: "Unknown",
build: "Unknown",
version: "Unknown",
edition: "Unknown"
},
cpus: [],
cpu_instruction_sets: {},
ram: 0,
gpus: []
}
}
# Hardware survey only works on Windows atm
if Gem::win_platform?
lib_dir = File.dirname($LOADED_FEATURES.find { |file| file.include?("gosu.so") })
SDL.load_lib("#{lib_dir}64/SDL2.dll")
else
SDL.load_lib("libSDL2")
end
query_displays
query_motherboard
query_operating_system
query_cpus
query_ram
query_gpus
@data.freeze
end
def query_displays
SDL.GetNumVideoDisplays.times do |d|
modes = []
refresh_rates = []
SDL.GetNumDisplayModes(d).times do |m|
mode = SDL::DisplayMode.new
SDL.GetDisplayMode(d, m, mode)
refresh_rates << mode[:refresh_rate]
modes << [mode[:w], mode[:h]]
end
@data[:displays] << {
name: SDL.GetDisplayName(d).read_string,
refresh_rates: refresh_rates.uniq.sort.reverse,
resolutions: modes.uniq.sort.reverse
}
end
end
def query_motherboard
return unless Gem::win_platform?
Win32::Registry::HKEY_LOCAL_MACHINE.open("HARDWARE\\DESCRIPTION\\System\\BIOS", Win32::Registry::KEY_READ) do |reg|
@data[:system][:motherboard][:manufacturer] = safe_reg(reg, "SystemManufacturer")
@data[:system][:motherboard][:model] = safe_reg(reg, "SystemProductName")
@data[:system][:motherboard][:bios_vendor] = safe_reg(reg, "BIOSVendor")
@data[:system][:motherboard][:bios_release_date] = safe_reg(reg, "BIOSReleaseDate")
@data[:system][:motherboard][:bios_version] = safe_reg(reg, "BIOSVersion")
end
rescue Win32::Registry::Error
@data[:system][:motherboard][:manufacturer] = "Unknown"
@data[:system][:motherboard][:model] = "Unknown"
@data[:system][:motherboard][:bios_vendor] = "Unknown"
@data[:system][:motherboard][:bios_release_date] = "Unknown"
@data[:system][:motherboard][:bios_version] = "Unknown"
end
def query_operating_system
return unless Gem::win_platform?
Win32::Registry::HKEY_LOCAL_MACHINE.open("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", Win32::Registry::KEY_READ) do |reg|
@data[:system][:operating_system][:name] = safe_reg(reg, "ProductName")
@data[:system][:operating_system][:build] = safe_reg(reg, "CurrentBuild")
@data[:system][:operating_system][:version] = safe_reg(reg, "DisplayVersion")
@data[:system][:operating_system][:edition] = safe_reg(reg, "EditionID")
end
rescue Win32::Registry::Error
@data[:system][:operating_system][:name] = "Unknown"
@data[:system][:operating_system][:build] = "Unknown"
@data[:system][:operating_system][:version] = "Unknown"
@data[:system][:operating_system][:edition] = "Unknown"
end
def query_cpus
if Gem::win_platform?
begin
Win32::Registry::HKEY_LOCAL_MACHINE.open("HARDWARE\\DESCRIPTION\\System\\CentralProcessor", Win32::Registry::KEY_READ) do |reg|
i = 0
reg.each_key do |key|
reg.open(key) do |cpu|
@data[:system][:cpus] << {
manufacturer: safe_reg(cpu, "VendorIdentifier", "Unknown"),
model: safe_reg(cpu, "ProcessorNameString").strip,
mhz: safe_reg(cpu, "~MHz"),
family: safe_reg(cpu, "Identifier")
}
i += 1
end
end
end
rescue Win32::Registry::Error
end
end
instruction_sets = %w[ HasRDTSC HasAltiVec HasMMX Has3DNow HasSSE HasSSE2 HasSSE3 HasSSE41 HasSSE42 HasAVX HasAVX2 HasAVX512F HasARMSIMD HasNEON ] # HasLSX HasLASX # These cause a crash atm
list = []
instruction_sets.each do |i|
if SDL.send(i).positive?
list << i.sub("Has", "")
end
@data[:system][:cpu_instruction_sets][:"#{i.sub("Has", "").downcase}"] = SDL.send(i).positive?
end
end
def query_ram
@data[:system][:ram] = SDL.GetSystemRAM
end
def query_gpus
return unless Gem::win_platform?
Win32::Registry::HKEY_LOCAL_MACHINE.open("SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}", Win32::Registry::KEY_READ) do |reg|
i = 0
reg.each_key do |key, _|
next unless key.start_with?("0")
reg.open(key) do |device|
vram = -1
begin
vram = device["HardwareInformation.qwMemorySize"].to_i
rescue Win32::Registry::Error, TypeError
begin
vram = device["HardwareInformation.MemorySize"].to_i
rescue Win32::Registry::Error, TypeError
vram = -1
end
end
next if vram.negative?
vram = vram / 1024.0 / 1024.0
@data[:system][:gpus] << {
manufacturer: safe_reg(device, "ProviderName"),
model: safe_reg(device, "DriverDesc"),
vram: vram.round,
driver_date: safe_reg(device, "DriverDate"),
driver_version: safe_reg(device, "DriverVersion")
}
i += 1
end
end
end
rescue Win32::Registry::Error
end
def safe_reg(reg, key, default_value = "Unknown")
reg[key]
rescue Win32::Registry::Error
default_value
end
end
end

View File

@@ -3,21 +3,28 @@ class W3DHub
class Games < Page
def setup
@game_news ||= {}
@game_events ||= {}
@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: 0.15, max_width: 148, height: 1.0, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
end
stack(width: 1.0, height: 1.0) do
# Games List
@games_list_container = flow(width: 1.0, height: 64, scroll: true, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR, padding_left: 32, padding_right: 32) do
end
# Game Menu
@game_page_container = stack(fill: true, height: 1.0) do
# Game Menu
@game_page_container = stack(width: 1.0, fill: true, background_image: "#{GAME_ROOT_PATH}/media/textures/noiseb.png", background_image_mode: :tiled) do
# , background_image: "C:/Users/cyber/Downloads/vlcsnap-2022-04-24-22h24m15s854.png"
end
end
end
# return if Store.offline_mode
populate_game_page(@focused_game, @focused_channel)
populate_games_list
end
@@ -26,25 +33,37 @@ class W3DHub
@games_list_container.clear do
background 0xff_121920
stack(width: 128, height: 1.0) do
flow(fill: true)
button "All Games" do
populate_all_games_view
end
flow(fill: true)
end
has_favorites = Store.settings[:favorites].size.positive?
Store.applications.games.each do |game|
next if has_favorites && !Store.application_manager.favorite?(game.id)
selected = game == @focused_game
game_button = stack(width: 1.0, border_thickness_left: 4,
border_color_left: selected ? 0xff_00acff : 0x00_000000,
game_button = stack(width: 64, height: 1.0, border_thickness_bottom: 4,
border_color_bottom: selected ? 0xff_00acff : 0x00_000000,
hover: { background: selected ? game.color : 0xff_444444 },
padding_top: 4, padding_bottom: 4) do
padding_left: 4, padding_right: 4, tip: game.name) 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_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_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff
image image_path, height: 48, color: Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x88_ffffff
flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do
image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 24, margin_left: -4, margin_top: -6, color: 0xff_ff8800 if Store.application_manager.updateable?(game.id, game.channels.first.id)
end
inscription game.name, width: 1.0, text_align: :center
# inscription game.name, width: 1.0, text_align: :center, text_size: 14
end
def game_button.hit_element?(x, y)
@@ -68,120 +87,279 @@ class W3DHub
@game_page_container.clear do
background game.color
# Release channel
flow(width: 1.0, height: 18) do
# background 0xff_444411
inscription I18n.t(:"games.channel")
list_box(items: game.channels.map(&:name), choose: channel.name, enabled: game.channels.count > 1,
margin_top: 0, margin_bottom: 0, width: 128,
padding_left: 1, padding_top: 1, padding_right: 1, padding_bottom: 1, text_size: 14) do |value|
populate_game_page(game, game.channels.find { |c| c.name == value })
end
end
@game_page_container.style.background_image_color = game.color
@game_page_container.style.default[:background_image_color] = game.color
@game_page_container.update_background_image
# Game Stuff
flow(width: 1.0, fill: true) do
# background 0xff_9999ff
# Game options
stack(width: 208, height: 1.0, padding: 8, scroll: true) do
# background 0xff_550055
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
background 0x55_000000
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) } }
# Game Banner
image_path = "#{GAME_ROOT_PATH}/media/banners/#{game.id}.png"
if File.exist?(image_path)
image image_path, width: 1.0
else
banner game.name unless File.exist?(image_path)
end
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
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
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|
end
game.web_links.each do |item|
flow(width: 1.0, height: 22, margin_bottom: 8) do
image "#{GAME_ROOT_PATH}/media/ui_icons/#{hash[:icon]}.png", width: 0.11 if hash[:icon]
image EMPTY_IMAGE, width: 0.11 unless hash[:icon]
link key, text_size: 18, enabled: hash.key?(:enabled) ? hash[:enabled] : true do
hash[:block]&.call
image "#{GAME_ROOT_PATH}/media/ui_icons/share1.png", width: 24
link item.name, text_size: 18 do
W3DHub.url(item.uri)
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: 0.11
link item.name, text_size: 18 do
W3DHub.url(item.uri)
if game.channels.count > 1
# Release channel
inscription I18n.t(:"games.game_version"), width: 1.0, text_align: :center
flow(width: 1.0, height: 48) do
# background 0xff_444411
list_box(width: 1.0, items: game.channels.map(&:name), choose: channel.name, enabled: game.channels.count > 1) do |value|
populate_game_page(game, game.channels.find { |c| c.name == value })
end
end
end
# Play buttons
flow(width: 1.0, height: 48, padding_top: 6, margin_bottom: 16) do
# background 0xff_551100
if Store.application_manager.installed?(game.id, channel.id)
if Store.application_manager.updateable?(game.id, channel.id)
button "<b>#{I18n.t(:"interface.install_update")}</b>", fill: true, text_size: 32, **UPDATE_BUTTON do
Store.application_manager.update(game.id, channel.id)
end
else
play_now_server = Store.application_manager.play_now_server(game.id, channel.id)
play_now_button = button "<b>#{I18n.t(:"interface.play")}</b>", fill: true, text_size: 32, enabled: !play_now_server.nil? do
Store.application_manager.play_now(game.id, channel.id)
end
play_now_button.subscribe(:enter) do |btn|
server = Store.application_manager.play_now_server(game.id, channel.id)
btn.enabled = !server.nil?
btn.instance_variable_set(:"@tip", server ? "#{server.status.name} [#{server.status.player_count}/#{server.status.max_players}]" : "")
end
end
button get_image("#{GAME_ROOT_PATH}/media/ui_icons/singleplayer.png"), tip: I18n.t(:"interface.single_player"), image_height: 32, margin_left: 0 do
Store.application_manager.run(game.id, channel.id)
end
button get_image("#{GAME_ROOT_PATH}/media/ui_icons/gear.png"), tip: I18n.t(:"games.game_options"), image_height: 32, margin_left: 0 do |btn|
items = []
items << { label: I18n.t(:"games.game_settings"), block: proc { push_state(States::GameSettingsDialog, app_id: game.id, channel: channel.id) } } #, block: proc { Store.application_manager.wwconfig(game.id, channel.id) } }
# items << { label: I18n.t(:"games.game_settings"), block: proc { Store.application_manager.settings(game.id, channel.id) } }
items << { label: I18n.t(:"games.wine_configuration"), block: proc { Store.application_manager.wine_configuration(game.id, channel.id) } } if W3DHub.unix?
items << { label: I18n.t(:"games.game_modifications"), block: proc { populate_game_modifications(game, channel) } } unless Store.offline_mode
if game.id != "ren"
items << { label: I18n.t(:"games.repair_installation"), block: proc { Store.application_manager.repair(game.id, channel.id) } } unless Store.offline_mode
items << { label: I18n.t(:"games.uninstall_game"), block: proc { Store.application_manager.uninstall(game.id, channel.id) } } unless Store.offline_mode
end
# From gui_state_ext.rb
# TODO: Implement in engine proper
menu(btn, items: items)
end
else
installing = Store.application_manager.task?(:installer, game.id, channel.id)
unless game.id == "ren"
button "<b>#{I18n.t(:"interface.install")}</b>", fill: true, margin_right: 8, text_size: 32, enabled: !installing do |button|
button.enabled = false
@import_button.enabled = false
Store.application_manager.install(game.id, channel.id)
end
end
@import_button = button "<b>#{I18n.t(:"interface.import")}</b>", fill: true, margin_left: 8, text_size: 32, 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
# 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 "<b>#{I18n.t(:"interface.install_update")}</b>", margin_left: 24, **UPDATE_BUTTON do
Store.application_manager.update(game.id, channel.id)
end
else
button "<b>#{I18n.t(:"interface.play_now")}</b>", margin_left: 24 do
Store.application_manager.play_now(game.id, channel.id)
stack(fill: true, height: 1.0) do
# Game Description
if false # description
# Height should match Game Banner container height
stack(width: 1.0, padding: 16) do
title "About #{game.name}", border_bottom_color: 0xff_666666, border_bottom_thickness: 1, width: 1.0
para "Command & Conquer: Tiberian Sun is a 1999 real-time stretegy video game by Westwood Studios, published by Electronic Arts, releaseed exclusively for Microsoft Windows on August 27th, 1999. The game is the sequel to the 1995 game Command & Conquer. It featured new semi-3D graphics, a more futuristic sci-fi setting, and new gameplay features such as vehicles capable of hovering and burrowing.", width: 1.0, text_size: 20
end
end
button "<b>#{I18n.t(:"interface.single_player")}</b>", 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 "<b>#{I18n.t(:"interface.install")}</b>", margin_left: 24, enabled: !installing do |button|
button.enabled = false
@import_button.enabled = false
Store.application_manager.install(game.id, channel.id)
end
# Game Events
@game_events_container = flow(width: 1.0, height: 128, padding: 8, visible: false) do
end
@import_button = button "<b>#{I18n.t(:"interface.import")}</b>", margin_left: 24, enabled: !installing do
Store.application_manager.import(game.id, channel.id)
# Game News
@game_news_container = flow(width: 1.0, fill: true, padding: 8, scroll: true) do
# background 0xff_005500
end
end
end
end
return if Cache.net_lock?("game_news_#{game.id}")
return if Store.offline_mode
if @game_news[game.id]
populate_game_news(game)
else
@game_news_container.clear do
title I18n.t(:"games.fetching_news"), padding: 8
unless Cache.net_lock?("game_news_#{game.id}")
if @game_events[game.id]
populate_game_events(game)
else
BackgroundWorker.foreground_job(
-> { fetch_game_events(game) },
lambda do |result|
if result
populate_game_events(game)
Cache.release_net_lock(result)
end
end
)
end
end
BackgroundWorker.foreground_job(
-> { fetch_game_news(game) },
lambda do |result|
if result
populate_game_news(game)
Cache.release_net_lock(result)
unless Cache.net_lock?("game_events_#{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
end
def populate_all_games_view
@game_page_container.clear do
background 0x88_353535
@game_page_container.style.background_image_color = 0x88_353535
@game_page_container.style.default[:background_image_color] = 0x88_353535
@game_page_container.update_background_image
@focused_game = nil
@focused_channel = nil
populate_games_list
flow(width: 1.0, height: 1.0) do
games_view_container = nil
# Options
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
background 0x55_000000
flow(width: 1.0, height: 48) do
button "All Games", width: 280 do
# games_view_container.clear
end
tagline Store.applications.games.count.to_s, fill: true, text_align: :right
end
flow(width: 1.0, height: 48, margin_top: 8) do
button "Installed", enabled: false, width: 280
tagline "0", fill: true, text_align: :right
end
flow(width: 1.0, height: 48, margin_top: 8) do
button "Favorites", enabled: false, width: 280
tagline Store.settings[:favorites].count, fill: true, text_align: :right
end
end
)
# Games list
games_view_container = stack(fill: true, height: 1.0, padding: 8, margin: 8) do
title "All Games"
flow(width: 1.0, fill: true, scroll: true) do
Store.applications.games.each do |game|
stack(width: 166, height: 224, margin: 8, background: 0x88_151515, border_color: game.color, border_thickness: 1) do
flow(width: 1.0, height: 24, padding: 8) do
para "Favorite", fill: true
toggle_button checked: Store.application_manager.favorite?(game.id), text_size: 18, padding_top: 3, padding_right: 3, padding_bottom: 3, padding_left: 3 do |btn|
Store.application_manager.favorive(game.id, btn.value)
Store.settings.save_settings
populate_games_list
end
end
container = stack(fill: true, width: 1.0, padding: 8) do
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"
flow(width: 1.0, margin_top: 8) do
flow(fill: true)
image image_path, width: 0.5
flow(fill: true)
end
caption game.name, margin_top: 8
end
def container.hit_element?(x, y)
return unless hit?(x, y)
self
end
container.subscribe(:clicked_left_mouse_button) do |element|
populate_game_page(game, game.channels.first)
populate_games_list
end
container.subscribe(:enter) do |element|
element.background = 0x88_454545
end
end
end
end
end
end
end
end
@@ -208,32 +386,79 @@ class W3DHub
if (feed = @game_news[game.id])
@game_news_container.clear do
# Patch Notes
if false # Patch notes
flow(width: 1.0, max_width: 346 * 3 + (8 * 4), height: 346, margin: 8, margin_right: 32, border_thickness: 1, border_color: darken(Gosu::Color.new(game.color))) do
background darken(Gosu::Color.new(game.color), 10)
stack(width: 346, height: 1.0, padding: 8) do
background 0xff_181d22
para "Patch Notes"
tagline "<b>Patch 2.0 is now out!</b>"
para "words go here " * 20
flow(fill: true)
button "Read More", width: 1.0
end
flow(fill: true)
title "Eye Candy Banner Goes Here."
end
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
image_path = Cache.path(item.image)
news_blurb_container = nil
news_title_container = nil
path = Cache.path(item.image)
news_container = stack(width: 300, height: 300, margin: 8, background_image: image_path, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
background 0x88_000000
if File.exist?(path)
image path, width: 0.4, padding: 4
# Detailed view
news_blurb_container = stack(width: 1.0, height: 1.0, background: 0xaa_000000, padding: 4) do
tagline "<b>#{item.title}</b>", width: 1.0
inscription "#{item.author}#{item.timestamp.strftime("%Y-%m-%d")}"
inscription item.blurb.gsub(/\n+/, "\n").strip[0..1024], fill: true
button I18n.t(:"games.read_more"), width: 1.0, margin_top: 8, margin_bottom: 0, padding_top: 4, padding_bottom: 4 do
W3DHub.url(item.uri)
end
end
# Just title
news_title_container = stack(width: 1.0, height: 1.0) do
flow(fill: true)
tagline "<b>#{item.title}</b>", width: 1.0, background: 0xaa_000000, padding: 4
end
end
news_blurb_container.hide
def news_container.hit_element?(x, y)
return unless hit?(x, y)
if @children.first.visible? && (btn = @children.first.children.find { |child| child.visible? && child.is_a?(CyberarmEngine::Element::Button) && child.hit?(x, y) })
btn
else
image BLACK_IMAGE, width: 0.4, padding: 4
self
end
end
stack(width: 0.6, height: 1.0) do
stack(width: 1.0, height: 112) do
link "<b>#{item.title}</b>", text_size: 18 do
W3DHub.url(item.uri)
end
inscription item.blurb.gsub(/\n+/, "\n").strip[0..180]
end
news_container.subscribe(:enter) do
news_title_container.hide
news_blurb_container.show
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
W3DHub.url(item.uri)
end
end
news_container.subscribe(:leave) do
unless news_container.hit?(window.mouse_x, window.mouse_y)
news_title_container.show
news_blurb_container.hide
end
end
end
@@ -241,6 +466,41 @@ class W3DHub
end
end
def fetch_game_events(game)
lock = Cache.acquire_net_lock("game_events_#{game.id}")
return false unless lock
events = Api.events(game.id)
Cache.release_net_lock("game_events_#{game.id}") unless events
return false unless events
@game_events[game.id] = events
"game_events_#{game.id}"
end
def populate_game_events(game)
return unless @focused_game == game
if (events = @game_events[game.id])
@game_events_container.show unless events.empty?
@game_events_container.hide if events.empty?
@game_events_container.clear do
events.flatten.each do |event|
stack(width: 300, height: 1.0, margin_left: 8, margin_right: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
background 0xaa_222222
title event.title, width: 1.0, text_align: :center
tagline event.start_time.strftime("%A"), text_size: 36, width: 1.0, text_align: :center
caption event.start_time.strftime("%B %e, %Y %l:%M %p"), width: 1.0, text_align: :center
end
end
end
end
end
def populate_game_modifications(application, channel)
@game_news_container.clear do
([

View File

@@ -1,545 +0,0 @@
class W3DHub
class Pages
class Games < Page
def setup
@game_news ||= {}
@game_events ||= {}
@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
stack(width: 1.0, height: 1.0) do
# Games List
@games_list_container = flow(width: 1.0, height: 64, scroll: true, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR, padding_left: 32, padding_right: 32) do
end
# Game Menu
@game_page_container = stack(width: 1.0, fill: true, background_image: "#{GAME_ROOT_PATH}/media/textures/noiseb.png", background_image_mode: :tiled) do
# , background_image: "C:/Users/cyber/Downloads/vlcsnap-2022-04-24-22h24m15s854.png"
end
end
end
# return if Store.offline_mode
populate_game_page(@focused_game, @focused_channel)
populate_games_list
end
def populate_games_list
@games_list_container.clear do
background 0xff_121920
stack(width: 128, height: 1.0) do
flow(fill: true)
button "All Games" do
populate_all_games_view
end
flow(fill: true)
end
has_favorites = Store.settings[:favorites].size.positive?
Store.applications.games.each do |game|
next if has_favorites && !Store.application_manager.favorite?(game.id)
selected = game == @focused_game
game_button = stack(width: 64, height: 1.0, border_thickness_bottom: 4,
border_color_bottom: selected ? 0xff_00acff : 0x00_000000,
hover: { background: selected ? game.color : 0xff_444444 },
padding_left: 4, padding_right: 4, tip: game.name) do
background game.color if selected
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_color = Store.application_manager.installed?(game.id, game.channels.first.id) ? 0xff_ffffff : 0x66_ffffff
flow(width: 1.0, height: 1.0, margin: 8, background_image: image_path, background_image_color: image_color, background_image_mode: :fill_height) do
image "#{GAME_ROOT_PATH}/media/ui_icons/import.png", width: 24, margin_left: -4, margin_top: -6, color: 0xff_ff8800 if Store.application_manager.updateable?(game.id, game.channels.first.id)
end
# inscription game.name, width: 1.0, text_align: :center, text_size: 14
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_page_container.style.background_image_color = game.color
@game_page_container.style.default[:background_image_color] = game.color
@game_page_container.update_background_image
# Game Stuff
flow(width: 1.0, fill: true) do
# background 0xff_9999ff
# Game options
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
background 0x55_000000
# Game Banner
image_path = "#{GAME_ROOT_PATH}/media/banners/#{game.id}.png"
if File.exist?(image_path)
image image_path, width: 1.0
else
banner game.name unless File.exist?(image_path)
end
stack(width: 1.0, fill: true, scroll: true, margin_top: 32) do
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
W3DHub.url(item.uri)
end
end
end
end
if game.channels.count > 1
# Release channel
inscription I18n.t(:"games.game_version"), width: 1.0, text_align: :center
flow(width: 1.0, height: 48) do
# background 0xff_444411
list_box(width: 1.0, items: game.channels.map(&:name), choose: channel.name, enabled: game.channels.count > 1) do |value|
populate_game_page(game, game.channels.find { |c| c.name == value })
end
end
end
# Play buttons
flow(width: 1.0, height: 48, padding_top: 6, margin_bottom: 16) do
# background 0xff_551100
if Store.application_manager.installed?(game.id, channel.id)
if Store.application_manager.updateable?(game.id, channel.id)
button "<b>#{I18n.t(:"interface.install_update")}</b>", fill: true, text_size: 32, **UPDATE_BUTTON do
Store.application_manager.update(game.id, channel.id)
end
else
play_now_server = Store.application_manager.play_now_server(game.id, channel.id)
play_now_button = button "<b>#{I18n.t(:"interface.play")}</b>", fill: true, text_size: 32, enabled: !play_now_server.nil? do
Store.application_manager.play_now(game.id, channel.id)
end
play_now_button.subscribe(:enter) do |btn|
server = Store.application_manager.play_now_server(game.id, channel.id)
btn.enabled = !server.nil?
btn.instance_variable_set(:"@tip", server ? "#{server.status.name} [#{server.status.player_count}/#{server.status.max_players}]" : "")
end
end
button get_image("#{GAME_ROOT_PATH}/media/ui_icons/singleplayer.png"), tip: I18n.t(:"interface.single_player"), image_height: 32, margin_left: 0 do
Store.application_manager.run(game.id, channel.id)
end
button get_image("#{GAME_ROOT_PATH}/media/ui_icons/gear.png"), tip: I18n.t(:"games.game_options"), image_height: 32, margin_left: 0 do |btn|
items = []
items << { label: I18n.t(:"games.game_settings"), block: proc { Store.application_manager.wwconfig(game.id, channel.id) } }
# items << { label: I18n.t(:"games.game_settings"), block: proc { Store.application_manager.settings(game.id, channel.id) } }
items << { label: I18n.t(:"games.wine_configuration"), block: proc { Store.application_manager.wine_configuration(game.id, channel.id) } } if W3DHub.unix?
items << { label: I18n.t(:"games.game_modifications"), block: proc { populate_game_modifications(game, channel) } } unless Store.offline_mode
if game.id != "ren"
items << { label: I18n.t(:"games.repair_installation"), block: proc { Store.application_manager.repair(game.id, channel.id) } } unless Store.offline_mode
items << { label: I18n.t(:"games.uninstall_game"), block: proc { Store.application_manager.uninstall(game.id, channel.id) } } unless Store.offline_mode
end
# From gui_state_ext.rb
# TODO: Implement in engine proper
menu(btn, items: items)
end
else
installing = Store.application_manager.task?(:installer, game.id, channel.id)
unless game.id == "ren"
button "<b>#{I18n.t(:"interface.install")}</b>", fill: true, margin_right: 8, text_size: 32, enabled: !installing do |button|
button.enabled = false
@import_button.enabled = false
Store.application_manager.install(game.id, channel.id)
end
end
@import_button = button "<b>#{I18n.t(:"interface.import")}</b>", fill: true, margin_left: 8, text_size: 32, enabled: !installing do
Store.application_manager.import(game.id, channel.id)
end
end
end
end
stack(fill: true, height: 1.0) do
# Game Description
if false # description
# Height should match Game Banner container height
stack(width: 1.0, padding: 16) do
title "About #{game.name}", border_bottom_color: 0xff_666666, border_bottom_thickness: 1, width: 1.0
para "Command & Conquer: Tiberian Sun is a 1999 real-time stretegy video game by Westwood Studios, published by Electronic Arts, releaseed exclusively for Microsoft Windows on August 27th, 1999. The game is the sequel to the 1995 game Command & Conquer. It featured new semi-3D graphics, a more futuristic sci-fi setting, and new gameplay features such as vehicles capable of hovering and burrowing.", width: 1.0, text_size: 20
end
end
# Game Events
@game_events_container = flow(width: 1.0, height: 128, padding: 8, visible: false) do
end
# Game News
@game_news_container = flow(width: 1.0, fill: true, padding: 8, scroll: true) do
# background 0xff_005500
end
end
end
end
return if Store.offline_mode
unless Cache.net_lock?("game_news_#{game.id}")
if @game_events[game.id]
populate_game_events(game)
else
BackgroundWorker.foreground_job(
-> { fetch_game_events(game) },
lambda do |result|
if result
populate_game_events(game)
Cache.release_net_lock(result)
end
end
)
end
end
unless Cache.net_lock?("game_events_#{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
end
def populate_all_games_view
@game_page_container.clear do
background 0x88_353535
@game_page_container.style.background_image_color = 0x88_353535
@game_page_container.style.default[:background_image_color] = 0x88_353535
@game_page_container.update_background_image
@focused_game = nil
@focused_channel = nil
populate_games_list
flow(width: 1.0, height: 1.0) do
games_view_container = nil
# Options
stack(width: 360, height: 1.0, padding: 8, scroll: true, border_thickness_right: 1, border_color_right: W3DHub::BORDER_COLOR) do
background 0x55_000000
flow(width: 1.0, height: 48) do
button "All Games", width: 280 do
# games_view_container.clear
end
tagline Store.applications.games.count.to_s, fill: true, text_align: :right
end
flow(width: 1.0, height: 48, margin_top: 8) do
button "Installed", enabled: false, width: 280
tagline "0", fill: true, text_align: :right
end
flow(width: 1.0, height: 48, margin_top: 8) do
button "Favorites", enabled: false, width: 280
tagline Store.settings[:favorites].count, fill: true, text_align: :right
end
end
# Games list
games_view_container = stack(fill: true, height: 1.0, padding: 8, margin: 8) do
title "All Games"
flow(width: 1.0, fill: true, scroll: true) do
Store.applications.games.each do |game|
stack(width: 166, height: 224, margin: 8, background: 0x88_151515, border_color: game.color, border_thickness: 1) do
flow(width: 1.0, height: 24, padding: 8) do
para "Favorite", fill: true
toggle_button checked: Store.application_manager.favorite?(game.id), text_size: 18, padding_top: 3, padding_right: 3, padding_bottom: 3, padding_left: 3 do |btn|
Store.application_manager.favorive(game.id, btn.value)
Store.settings.save_settings
populate_games_list
end
end
container = stack(fill: true, width: 1.0, padding: 8) do
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"
flow(width: 1.0, margin_top: 8) do
flow(fill: true)
image image_path, width: 0.5
flow(fill: true)
end
caption game.name, margin_top: 8
end
def container.hit_element?(x, y)
return unless hit?(x, y)
self
end
container.subscribe(:clicked_left_mouse_button) do |element|
populate_game_page(game, game.channels.first)
populate_games_list
end
container.subscribe(:enter) do |element|
element.background = 0x88_454545
end
end
end
end
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
# Patch Notes
if false # Patch notes
flow(width: 1.0, max_width: 346 * 3 + (8 * 4), height: 346, margin: 8, margin_right: 32, border_thickness: 1, border_color: darken(Gosu::Color.new(game.color))) do
background darken(Gosu::Color.new(game.color), 10)
stack(width: 346, height: 1.0, padding: 8) do
background 0xff_181d22
para "Patch Notes"
tagline "<b>Patch 2.0 is now out!</b>"
para "words go here " * 20
flow(fill: true)
button "Read More", width: 1.0
end
flow(fill: true)
title "Eye Candy Banner Goes Here."
end
end
feed.items.sort_by { |i| i.timestamp }.reverse[0..9].each do |item|
image_path = Cache.path(item.image)
news_blurb_container = nil
news_title_container = nil
news_container = stack(width: 300, height: 300, margin: 8, background_image: image_path, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
background 0x88_000000
# Detailed view
news_blurb_container = stack(width: 1.0, height: 1.0, background: 0xaa_000000, padding: 4) do
tagline "<b>#{item.title}</b>", width: 1.0
inscription "#{item.author}#{item.timestamp.strftime("%Y-%m-%d")}"
inscription item.blurb.gsub(/\n+/, "\n").strip[0..1024], fill: true
button I18n.t(:"games.read_more"), width: 1.0, margin_top: 8, margin_bottom: 0, padding_top: 4, padding_bottom: 4 do
W3DHub.url(item.uri)
end
end
# Just title
news_title_container = stack(width: 1.0, height: 1.0) do
flow(fill: true)
tagline "<b>#{item.title}</b>", width: 1.0, background: 0xaa_000000, padding: 4
end
end
news_blurb_container.hide
def news_container.hit_element?(x, y)
return unless hit?(x, y)
if @children.first.visible? && (btn = @children.first.children.find { |child| child.visible? && child.is_a?(CyberarmEngine::Element::Button) && child.hit?(x, y) })
btn
else
self
end
end
news_container.subscribe(:enter) do
news_title_container.hide
news_blurb_container.show
end
news_container.subscribe(:leave) do
unless news_container.hit?(window.mouse_x, window.mouse_y)
news_title_container.show
news_blurb_container.hide
end
end
end
end
end
end
def fetch_game_events(game)
lock = Cache.acquire_net_lock("game_events_#{game.id}")
return false unless lock
events = Api.events(game.id)
Cache.release_net_lock("game_events_#{game.id}") unless events
return false unless events
@game_events[game.id] = events
"game_events_#{game.id}"
end
def populate_game_events(game)
return unless @focused_game == game
if (events = @game_events[game.id])
@game_events_container.show unless events.empty?
@game_events_container.hide if events.empty?
@game_events_container.clear do
events.flatten.each do |event|
stack(width: 300, height: 1.0, margin_left: 8, margin_right: 8, border_thickness: 1, border_color: lighten(Gosu::Color.new(game.color))) do
background 0xaa_222222
title event.title, width: 1.0, text_align: :center
tagline event.start_time.strftime("%A"), text_size: 36, width: 1.0, text_align: :center
caption event.start_time.strftime("%B %e, %Y %l:%M %p"), width: 1.0, text_align: :center
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]) { W3DHub.url(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

View File

@@ -147,6 +147,9 @@ class W3DHub
else
# FIXME: Failed to retreive!
BackgroundWorker.foreground_job(-> {}, ->(_){ @status_label.value = "FAILED TO RETREIVE APPS LIST" })
@offline_mode = true
Store.offline_mode = true
end
end
end
@@ -160,6 +163,8 @@ class W3DHub
end
Api.on_thread(:package_details, packages) do |package_details|
package_details ||= nil
package_details&.each do |package|
path = Cache.package_path(package.category, package.subcategory, package.name, package.version)
generated_icon_path = "#{GAME_ROOT_PATH}/media/icons/#{package.subcategory}.png"

26
lib/states/dialog.rb Normal file
View File

@@ -0,0 +1,26 @@
class W3DHub
class States
class Dialog < CyberarmEngine::GuiState
def draw
previous_state&.draw
Gosu.flush
super
end
def update
super
return unless window.current_state == self
window.states.reverse.each do |state|
# Don't update ourselves, forever
next if state == self && state.is_a?(CyberarmEngine::GuiState)
state.update
end
end
end
end
end

View File

@@ -1,6 +1,6 @@
class W3DHub
class States
class ConfirmDialog < CyberarmEngine::GuiState
class ConfirmDialog < Dialog
def setup
window.show_cursor = true
@@ -36,14 +36,6 @@ class W3DHub
end
end
end
def draw
previous_state&.draw
Gosu.flush
super
end
end
end
end

View File

@@ -1,6 +1,6 @@
class W3DHub
class States
class DirectConnectDialog < CyberarmEngine::GuiState
class DirectConnectDialog < Dialog
def setup
window.show_cursor = true
W3DHub::Store[:asterisk_config] ||= Asterisk::Config.new
@@ -214,14 +214,6 @@ class W3DHub
end
end
def draw
previous_state&.draw
Gosu.flush
super
end
def populate_from_server_profile(profile)
@server_nickname.value = profile.nickname
@server_password.value = Base64.strict_decode64(profile.password)

View File

@@ -0,0 +1,263 @@
class W3DHub
class States
class GameSettingsDialog < Dialog
BUTTON_STYLE = { text_size: 18, padding_top: 3, padding_bottom: 3, padding_left: 3, padding_right: 3 }
def setup
window.show_cursor = true
theme(THEME)
@app_id = @options[:app_id]
@channel = @options[:channel]
@game_settings = GameSettings.new(@app_id, @channel)
background 0xee_444444
stack(width: 1.0, max_width: 720, height: 1.0, max_height: 680, v_align: :center, h_align: :center, background: 0xee_222222, border_thickness: 2, border_color: 0xff_444444, padding: 10) do
flow(width: 1.0, height: 0.1, padding: 8) do
background Store.application_manager.color(@app_id)
title @options[:title] || Store.application_manager.name(@app_id) || "Game Settings", fill: true, text_align: :center
end
stack(width: 1.0, fill: true, padding: 16, margin_top: 10) do
flow(width: 1.0, fill: true) do
stack(width: 0.5, height: 1.0, margin_right: 8) do
tagline "General"
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Default to First Person", fill: true
toggle_button tip: "Default to First Person", checked: @game_settings.get_value(:default_to_first_person), **BUTTON_STYLE do |btn|
@game_settings.set_value(:default_to_first_person, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Background Downloads", fill: true
toggle_button tip: "Background Downloads", checked: @game_settings.get_value(:background_downloads), **BUTTON_STYLE do |btn|
@game_settings.set_value(:background_downloads, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Enable Hints", fill: true
toggle_button tip: "Enable Hints", checked: @game_settings.get_value(:hints_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:hints_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Enable Chat Log", fill: true
toggle_button tip: "Enable Chat Log", checked: @game_settings.get_value(:chat_log), **BUTTON_STYLE do |btn|
@game_settings.set_value(:chat_log, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Show FPS", fill: true
toggle_button tip: "Show FPS", checked: @game_settings.get_value(:show_fps), **BUTTON_STYLE do |btn|
@game_settings.set_value(:show_fps, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Show Velocity", fill: true
toggle_button tip: "Show Velocity", checked: @game_settings.get_value(:show_velocity), **BUTTON_STYLE do |btn|
@game_settings.set_value(:show_velocity, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Show Damage Numbers", fill: true
toggle_button tip: "Show Damage Numbers", checked: @game_settings.get_value(:show_damage_numbers), **BUTTON_STYLE do |btn|
@game_settings.set_value(:show_damage_numbers, btn.value)
end
end
end
stack(width: 0.5, height: 1.0, margin_left: 8) do
tagline "Video"
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
res_options = @game_settings.get(:resolution_width).options.each_with_index.map do |w, i|
"#{w[0]}x#{@game_settings.get(:resolution_height).options[i][0]}"
end
current_res = "#{@game_settings.get_value(:resolution_width)}x#{@game_settings.get_value(:resolution_height)}"
para "Resolution", fill: true
list_box items: res_options, choose: current_res, width: 172, **BUTTON_STYLE do |value|
w, h = value.split("x", 2)
@game_settings.set_value(:resolution_width, w.to_i)
@game_settings.set_value(:resolution_height, h.to_i)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Windowed Mode", fill: true
list_box items: @game_settings.get(:windowed_mode).options.map { |v| v[0] }, choose: @game_settings.get_value(:windowed_mode), width: 172, **BUTTON_STYLE do |value|
@game_settings.set_value(:windowed_mode, value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Enable VSync", fill: true
toggle_button tip: "Enable VSync", checked: @game_settings.get_value(:vsync), **BUTTON_STYLE do |btn|
@game_settings.set_value(:vsync, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Anti-aliasing", fill: true
list_box items: @game_settings.get(:anti_aliasing).options.map { |v| v[0] }, choose: @game_settings.get_value(:anti_aliasing), width: 72, **BUTTON_STYLE do |value|
@game_settings.set_value(:anti_aliasing, value)
end
end
end
end
flow(width: 1.0, fill: true, margin_top: 16) do
stack(width: 0.5, height: 1.0, margin_right: 8) do
tagline "Audio"
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Master Volume", fill: true
slider(height: 1.0, width: 172, value: @game_settings.get_value(:master_volume), margin_right: 8).subscribe(:changed) do |slider|
@game_settings.set_value(:master_volume, slider.value)
end
toggle_button tip: "Sound Effects", checked: @game_settings.get(:master_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:master_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Sound Effects", fill: true
slider(height: 1.0, width: 172, value: @game_settings.get_value(:sound_effects_volume), margin_right: 8).subscribe(:changed) do |slider|
@game_settings.set_value(:sound_effects_volume, slider.value)
end
toggle_button tip: "Sound Effects", checked: @game_settings.get(:sound_effects_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:sound_effects_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Dialogue", fill: true
slider(height: 1.0, width: 172, value: @game_settings.get_value(:sound_dialog_volume), margin_right: 8).subscribe(:changed) do |slider|
@game_settings.set_value(:sound_dialog_volume, slider.value)
end
toggle_button tip: "Dialogue", checked: @game_settings.get_value(:sound_dialog_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:sound_dialog_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Music", fill: true
slider(height: 1.0, width: 172, value: @game_settings.get_value(:sound_music_volume), margin_right: 8).subscribe(:changed) do |slider|
@game_settings.set_value(:sound_music_volume, slider.value)
end
toggle_button tip: "Music", checked: @game_settings.get_value(:sound_music_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:sound_music_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Cinematic", fill: true
slider(height: 1.0, width: 172, value: @game_settings.get_value(:sound_cinematic_volume), margin_right: 8).subscribe(:changed) do |slider|
@game_settings.set_value(:sound_cinematic_volume, slider.value)
end
toggle_button tip: "Cinematic", checked: @game_settings.get_value(:sound_cinematic_enabled), **BUTTON_STYLE do |btn|
@game_settings.set_value(:sound_cinematic_enabled, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Play Sound with Game in Background", fill: true
toggle_button tip: "Play Sound with Game in Background", checked: @game_settings.get_value(:sound_in_background), **BUTTON_STYLE do |btn|
@game_settings.set_value(:sound_in_background, btn.value)
end
end
end
stack(width: 0.5, height: 1.0, margin_left: 8) do
tagline "Performance"
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Texture Detail", fill: true
list_box items: @game_settings.get(:texture_detail).options.map { |v| v[0] }, choose: @game_settings.get_value(:texture_detail), width: 172, **BUTTON_STYLE do |value|
@game_settings.set_value(:texture_detail, value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Texture Filtering", fill: true
list_box items: @game_settings.get(:texture_filtering).options.map { |v| v[0] }, choose: @game_settings.get_value(:texture_filtering), width: 172, **BUTTON_STYLE do |value|
@game_settings.set_value(:texture_filtering, value)
end
end
# flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
# para "Shader Detail", fill: true
# list_box items: @game_settings.get(:texture_filtering).options.map { |v| v[0] }, choose: @game_settings.get_value(:texture_filtering), width: 172, **BUTTON_STYLE do |value|
# @game_settings.set_value(:texture_filtering, value)
# end
# end
# flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
# para "Post Processing Detail", fill: true
# list_box items: @game_settings.get(:texture_filtering).options.map { |v| v[0] }, choose: @game_settings.get_value(:texture_filtering), width: 172, **BUTTON_STYLE do |value|
# @game_settings.set_value(:texture_filtering, value)
# end
# end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "Shadow Resolution", fill: true
list_box items: @game_settings.get(:shadow_resolution).options.map { |v| v[0] }, choose: @game_settings.get_value(:shadow_resolution), width: 172, **BUTTON_STYLE do |value|
@game_settings.set_value(:shadow_resolution, value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "High Quality Shadows", fill: true
toggle_button tip: "High Quality Shadows", checked: @game_settings.get_value(:background_downloads), **BUTTON_STYLE do |btn|
@game_settings.set_value(:background_downloads, btn.value)
end
end
flow(width: 1.0, height: 24, margin: 4, margin_left: 10) do
para "FPS Limit", fill: true
list_box items: @game_settings.get(:fps).options.map { |v| v[0] }, choose: @game_settings.get_value(:fps), width: 172, **BUTTON_STYLE do |value|
@game_settings.set_value(:fps, value.to_i)
end
end
end
end
end
flow(width: 1.0, height: 0.1, padding: 8) do
button "Cancel", width: 0.25 do
pop_state
@options[:cancel_callback]&.call
end
flow(fill: true)
button "Save", width: 0.25 do
pop_state
@game_settings.save_settings!
@options[:accept_callback]&.call
end
end
end
end
end
end
end

View File

@@ -1,6 +1,6 @@
class W3DHub
class States
class MessageDialog < CyberarmEngine::GuiState
class MessageDialog < Dialog
def setup
window.show_cursor = true
@@ -28,14 +28,6 @@ class W3DHub
end
end
end
def draw
previous_state&.draw
Gosu.flush
super
end
end
end
end

View File

@@ -1,6 +1,6 @@
class W3DHub
class States
class PromptDialog < CyberarmEngine::GuiState
class PromptDialog < Dialog
def setup
window.show_cursor = true
@@ -65,14 +65,6 @@ class W3DHub
end
end
end
def draw
previous_state&.draw
Gosu.flush
super
end
end
end
end

View File

@@ -1,163 +0,0 @@
class W3DHub
class States
class GameSettingsDialog < CyberarmEngine::GuiState
BUTTON_STYLE = { text_size: 18, padding_top: 3, padding_bottom: 3, padding_left: 3, padding_right: 3 }
def setup
window.show_cursor = true
theme(W3DHub::THEME)
background 0xee_444444
stack(width: 1.0, max_width: 720, height: 1.0, max_height: 512, v_align: :center, h_align: :center, background: 0xee_222222) do
flow(width: 1.0, height: 0.1, padding: 8) do
background 0x88_000000
image "#{GAME_ROOT_PATH}/media/icons/#{@options[:app_id]}.png", width: 0.04, align: :center
tagline "<b>#{@options[:title]}</b>", width: 0.9, text_align: :center
end
stack(width: 1.0, fill: true, padding: 16) do
flow(width: 1.0, fill: true) do
stack(width: 0.5, height: 1.0) do
caption "General"
flow(width: 1.0, height: 24, margin: 4) do
para "Default to First Person", fill: true
toggle_button tip: "Default to First Person", **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Enable Chat Log", fill: true
toggle_button tip: "Enable Chat Log", **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Background Downloads", fill: true
toggle_button tip: "Background Downloads", **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Show FPS", fill: true
toggle_button tip: "Show FPS", **BUTTON_STYLE
end
end
stack(width: 0.5, height: 1.0) do
caption "Video"
flow(width: 1.0, height: 24, margin: 4) do
para "Resolution", fill: true
list_box items: ["#{Gosu.screen_width}x#{Gosu.screen_height}"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Windowed Mode", fill: true
list_box items: ["Windowed", "Borderless", "Fullscreen"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Enable VSync", fill: true
toggle_button tip: "Enable VSync", **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "MSAA Mode", fill: true
list_box items: %w[0 2 4 8 16], width: 48, **BUTTON_STYLE
end
end
end
flow(width: 1.0, fill: true) do
stack(width: 0.5, height: 1.0) do
caption "Audio"
flow(width: 1.0, height: 24, margin: 4) do
para "Sound Effects", fill: true
slider height: 1.0, width: 172, margin_right: 8
toggle_button tip: "Sound Effects", checked: true, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Dialogue", fill: true
slider height: 1.0, width: 172, margin_right: 8
toggle_button tip: "Dialogue", checked: true, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Music", fill: true
slider height: 1.0, width: 172, margin_right: 8
toggle_button tip:"Music", checked: true, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Cinematic", fill: true
slider height: 1.0, width: 172, margin_right: 8
toggle_button tip: "Cinematic", checked: true, **BUTTON_STYLE
end
end
stack(width: 0.5, height: 1.0) do
caption "Performance"
flow(width: 1.0, height: 24, margin: 4) do
para "Texture Detail", fill: true
list_box items: ["Low", "Medium", "High"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Shader Detail", fill: true
list_box items: ["Low", "Medium", "High"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Post Proccessing Detail", fill: true
list_box items: ["Low", "Medium", "High"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "Shadow Detail", fill: true
list_box items: ["Low", "Medium", "High"], width: 128, **BUTTON_STYLE
end
flow(width: 1.0, height: 24, margin: 4) do
para "High Quality Shadows", fill: true
toggle_button tip: "High Quality Shadows", **BUTTON_STYLE
end
end
end
end
flow(width: 1.0, height: 0.1, padding: 8) do
button "Cancel", width: 0.25 do
pop_state
@options[:cancel_callback]&.call
end
flow(fill: true)
button "WWConfig", width: 0.25 do
pop_state
Store.application_manager.wwconfig(@options[:app_id], @options[:channel])
end
flow(fill: true)
button "Save", width: 0.25 do
pop_state
@options[:accept_callback]&.call
end
end
end
end
def draw
previous_state&.draw
Gosu.flush
super
end
end
end
end

View File

@@ -23,91 +23,84 @@ class W3DHub
@page = nil
@pages = {}
Store.application_manager.auto_import
Store.application_manager.auto_import # unless Store.offline_mode
theme(W3DHub::THEME)
@interface_container = flow(width: 1.0, height: 1.0) do
# TODO: Override this background color to be a darkened version of the selected games
# or a default color
background 0xff_212121..0xff_111111
@interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR) do
background 0xff_252525
flow(fill: true, height: 1.0)
@header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) 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
stack(width: 1.0, max_width: MAX_PAGE_WIDTH, height: 1.0, border_thickness: 1, border_color: 0xff_aaaaaa) do
background 0xff_252525
@navigation_container = stack(fill: true, height: 1.0) do
flow(width: 1.0, fill: true) do
# background 0xff_666666
@header_container = flow(width: 1.0, height: 100, padding: 4) do
image "#{GAME_ROOT_PATH}/media/icons/app.png", width: 108
stack(fill: true, height: 1.0) do
# background 0xff_885500
@app_info_container = flow(width: 1.0, height: 0.65) do
# background 0xff_8855ff
stack(fill: true, height: 1.0) do
title "<b>#{I18n.t(:"app_name")}</b>", height: 0.5
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 "<b>#{I18n.t(:"interface.not_logged_in")}</b>", 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
W3DHub.url("https://secure.w3dhub.com/forum/index.php?app=core&module=global&section=register")
end
end
end
end
link I18n.t(:"interface.games").upcase, text_size: 34 do
page(W3DHub::Pages::Games)
end
@navigation_container = flow(width: 1.0, height: 0.35) do
# background 0xff_666666
flow(width: 1.0, height: 1.0) do
flow(fill: true, height: 1.0) # Hacky centering
link I18n.t(:"interface.games") 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.server_browser"), margin_left: 18 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.community"), margin_left: 18 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.downloads"), margin_left: 18 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
link I18n.t(:"interface.settings"), margin_left: 18 do
page(W3DHub::Pages::Settings)
end
flow(fill: true, height: 1.0) # Hacky centering
# 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
@content_container = flow(width: 1.0, fill: true) do
@account_container = flow(width: 256, height: 1.0) do
if Store.offline_mode
stack(width: 1.0, height: 1.0) do
flow(fill: true)
title "<b>OFFLINE</b>", text_wrap: :none, width: 1.0, text_align: :center
flow(fill: true)
end
else
stack(width: 1.0, height: 1.0) do
tagline "<b>#{I18n.t(:"interface.not_logged_in")}</b>", 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
W3DHub.url("https://secure.w3dhub.com/forum/index.php?app=core&module=global&section=register")
end
end
end
end
end
end
flow(fill: true, height: 1.0)
@content_container = flow(width: 1.0, fill: true) do
end
end
if Store.account
@@ -172,6 +165,12 @@ class W3DHub
@page.refresh_server_list(server)
end
def update_server_ping(server)
return unless @page.is_a?(Pages::ServerBrowser)
@page.update_server_ping(server)
end
def show_application_taskbar
@application_taskbar_container.show
end

View File

@@ -1,223 +0,0 @@
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 # unless Store.offline_mode
theme(W3DHub::THEME)
@interface_container = stack(width: 1.0, height: 1.0, border_thickness: 1, border_color: W3DHub::BORDER_COLOR) do
background 0xff_252525
@header_container = flow(width: 1.0, height: 84, padding: 4, border_thickness_bottom: 1, border_color_bottom: W3DHub::BORDER_COLOR) 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
if Store.offline_mode
stack(width: 1.0, height: 1.0) do
flow(fill: true)
title "<b>OFFLINE</b>", text_wrap: :none, width: 1.0, text_align: :center
flow(fill: true)
end
else
stack(width: 1.0, height: 1.0) do
tagline "<b>#{I18n.t(:"interface.not_logged_in")}</b>", 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
W3DHub.url("https://secure.w3dhub.com/forum/index.php?app=core&module=global&section=register")
end
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 update_server_ping(server)
return unless @page.is_a?(Pages::ServerBrowser)
@page.update_server_ping(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

110
lib/win32_stub.rb Normal file
View File

@@ -0,0 +1,110 @@
module Win32
class Registry
module Constants
HKEY_CLASSES_ROOT = 0x80000000
HKEY_CURRENT_USER = 0x80000001
HKEY_LOCAL_MACHINE = 0x80000002
HKEY_USERS = 0x80000003
HKEY_PERFORMANCE_DATA = 0x80000004
HKEY_PERFORMANCE_TEXT = 0x80000050
HKEY_PERFORMANCE_NLSTEXT = 0x80000060
HKEY_CURRENT_CONFIG = 0x80000005
HKEY_DYN_DATA = 0x80000006
REG_NONE = 0
REG_SZ = 1
REG_EXPAND_SZ = 2
REG_BINARY = 3
REG_DWORD = 4
REG_DWORD_LITTLE_ENDIAN = 4
REG_DWORD_BIG_ENDIAN = 5
REG_LINK = 6
REG_MULTI_SZ = 7
REG_RESOURCE_LIST = 8
REG_FULL_RESOURCE_DESCRIPTOR = 9
REG_RESOURCE_REQUIREMENTS_LIST = 10
REG_QWORD = 11
REG_QWORD_LITTLE_ENDIAN = 11
STANDARD_RIGHTS_READ = 0x00020000
STANDARD_RIGHTS_WRITE = 0x00020000
KEY_QUERY_VALUE = 0x0001
KEY_SET_VALUE = 0x0002
KEY_CREATE_SUB_KEY = 0x0004
KEY_ENUMERATE_SUB_KEYS = 0x0008
KEY_NOTIFY = 0x0010
KEY_CREATE_LINK = 0x0020
KEY_READ = STANDARD_RIGHTS_READ |
KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY
KEY_WRITE = STANDARD_RIGHTS_WRITE |
KEY_SET_VALUE | KEY_CREATE_SUB_KEY
KEY_EXECUTE = KEY_READ
KEY_ALL_ACCESS = KEY_READ | KEY_WRITE | KEY_CREATE_LINK
REG_OPTION_RESERVED = 0x0000
REG_OPTION_NON_VOLATILE = 0x0000
REG_OPTION_VOLATILE = 0x0001
REG_OPTION_CREATE_LINK = 0x0002
REG_OPTION_BACKUP_RESTORE = 0x0004
REG_OPTION_OPEN_LINK = 0x0008
REG_LEGAL_OPTION = REG_OPTION_RESERVED |
REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK |
REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK
REG_CREATED_NEW_KEY = 1
REG_OPENED_EXISTING_KEY = 2
REG_WHOLE_HIVE_VOLATILE = 0x0001
REG_REFRESH_HIVE = 0x0002
REG_NO_LAZY_FLUSH = 0x0004
REG_FORCE_RESTORE = 0x0008
MAX_KEY_LENGTH = 514
MAX_VALUE_LENGTH = 32768
end
include Constants
include Enumerable
class Error < ::StandardError
attr_reader :code
end
class PredefinedKey < Registry
def initialize(hkey, keyname)
@hkey = hkey
@parent = nil
@keyname = keyname
@disposition = REG_OPENED_EXISTING_KEY
end
# Predefined keys cannot be closed
def close
raise Error.new(5) ## ERROR_ACCESS_DENIED
end
# Fake #class method for Registry#open, Registry#create
def class
Registry
end
# Make all
Constants.constants.grep(/^HKEY_/) do |c|
Registry.const_set c, new(Constants.const_get(c), c.to_s)
end
end
def open(*args)
raise Win32::Registry::Error
end
def create(*args)
Stub.new
end
class Stub
def write_i(*arg)
# No OP
end
end
end
end

View File

@@ -70,6 +70,7 @@ end
require "i18n"
require "websocket-client-simple"
require "English"
require "sdl2"
I18n.load_path << Dir["#{W3DHub::GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en
@@ -77,6 +78,8 @@ I18n.default_locale = :en
# GUI_DEBUG = true
require_relative "lib/gui_state_ext"
require_relative "lib/win32_stub" unless Gem.win_platform?
require_relative "lib/version"
require_relative "lib/theme"
require_relative "lib/common"
@@ -87,6 +90,8 @@ require_relative "lib/settings"
require_relative "lib/mixer"
require_relative "lib/ico"
require_relative "lib/multicast_server"
require_relative "lib/hardware_survey"
require_relative "lib/game_settings"
require_relative "lib/background_worker"
require_relative "lib/application_manager"
require_relative "lib/application_manager/manifest"
@@ -100,14 +105,14 @@ 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_redesign"
require_relative "lib/states/interface"
require_relative "lib/states/welcome"
require_relative "lib/states/message_dialog"
require_relative "lib/states/prompt_dialog"
require_relative "lib/states/confirm_dialog"
require_relative "lib/states/direct_connect_dialog"
require_relative "lib/states/game_settings_dialog"
require_relative "lib/states/dialog"
require_relative "lib/states/dialogs/message_dialog"
require_relative "lib/states/dialogs/prompt_dialog"
require_relative "lib/states/dialogs/confirm_dialog"
require_relative "lib/states/dialogs/direct_connect_dialog"
require_relative "lib/states/dialogs/game_settings_dialog"
require_relative "lib/api"
require_relative "lib/api/service_status"
@@ -120,8 +125,7 @@ require_relative "lib/api/package"
require_relative "lib/api/event"
require_relative "lib/page"
# require_relative "lib/pages/games"
require_relative "lib/pages/games_redesign"
require_relative "lib/pages/games"
require_relative "lib/pages/server_browser"
require_relative "lib/pages/community"
require_relative "lib/pages/login"