|
5 | 5 | #include <Game/States/MenuState.hpp> |
6 | 6 | #include <Game/States/ConnectionState.hpp> |
7 | 7 | #include <Game/States/GameState.hpp> |
| 8 | +#include <Game/States/UpdateState.hpp> |
8 | 9 | #include <CommonLib/GameConstants.hpp> |
| 10 | +#include <CommonLib/Version.hpp> |
9 | 11 | #include <Nazara/Core/ApplicationBase.hpp> |
10 | 12 | #include <Nazara/Core/StateMachine.hpp> |
11 | 13 | #include <Nazara/Network/Algorithm.hpp> |
12 | 14 | #include <Nazara/Network/IpAddress.hpp> |
| 15 | +#include <Nazara/Network/Network.hpp> |
| 16 | +#include <Nazara/Network/WebRequest.hpp> |
| 17 | +#include <Nazara/Network/WebService.hpp> |
13 | 18 | #include <Nazara/Utility/SimpleTextDrawer.hpp> |
14 | 19 | #include <Nazara/Widgets.hpp> |
15 | 20 | #include <fmt/color.h> |
16 | 21 | #include <fmt/format.h> |
| 22 | +#include <nlohmann/json.hpp> |
| 23 | +#include <semver.hpp> |
| 24 | +#include <optional> |
| 25 | + |
| 26 | +namespace nlohmann |
| 27 | +{ |
| 28 | + template<> |
| 29 | + struct adl_serializer<tsom::UpdateInfo::DownloadInfo> |
| 30 | + { |
| 31 | + static void from_json(const nlohmann::json& json, tsom::UpdateInfo::DownloadInfo& downloadInfo) |
| 32 | + { |
| 33 | + downloadInfo.downloadUrl = json["download_url"]; |
| 34 | + downloadInfo.size = json["size"]; |
| 35 | + downloadInfo.sha256 = json.value("sha256", ""); |
| 36 | + } |
| 37 | + }; |
| 38 | + |
| 39 | + template<> |
| 40 | + struct adl_serializer<semver::version> |
| 41 | + { |
| 42 | + static void from_json(const nlohmann::json& json, semver::version& downloadInfo) |
| 43 | + { |
| 44 | + std::string_view versionStr = json; |
| 45 | + downloadInfo.from_string(versionStr); |
| 46 | + } |
| 47 | + }; |
| 48 | +} |
17 | 49 |
|
18 | 50 | namespace tsom |
19 | 51 | { |
@@ -52,30 +84,127 @@ namespace tsom |
52 | 84 |
|
53 | 85 | m_connectButton = m_layout->Add<Nz::ButtonWidget>(); |
54 | 86 | m_connectButton->UpdateText(Nz::SimpleTextDrawer::Draw("Connect", 36, Nz::TextStyle_Regular, Nz::Color::sRGBToLinear(Nz::Color(0.13f)))); |
| 87 | + m_connectButton->SetMaximumWidth(m_connectButton->GetPreferredWidth() * 1.5f); |
55 | 88 | m_connectButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) |
56 | 89 | { |
57 | 90 | OnConnectPressed(); |
58 | 91 | }); |
59 | 92 |
|
| 93 | + m_updateLayout = CreateWidget<Nz::BoxLayout>(Nz::BoxLayoutOrientation::TopToBottom); |
| 94 | + |
| 95 | + m_updateLabel = m_updateLayout->Add<Nz::LabelWidget>(); |
| 96 | + m_updateLabel->UpdateText(Nz::SimpleTextDrawer::Draw("A new version is available!", 18)); |
| 97 | + |
| 98 | + m_updateButton = m_updateLayout->Add<Nz::ButtonWidget>(); |
| 99 | + m_updateButton->UpdateText(Nz::SimpleTextDrawer::Draw("Update game", 18, Nz::TextStyle_Regular, Nz::Color::sRGBToLinear(Nz::Color(0.13f)))); |
| 100 | + m_updateButton->SetMaximumWidth(m_updateButton->GetPreferredWidth()); |
| 101 | + m_updateButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) |
| 102 | + { |
| 103 | + OnUpdatePressed(); |
| 104 | + }); |
| 105 | + |
60 | 106 | m_autoConnect = cmdParams.HasFlag("auto-connect"); |
61 | 107 | } |
62 | 108 |
|
| 109 | + void MenuState::Enter(Nz::StateMachine& fsm) |
| 110 | + { |
| 111 | + WidgetState::Enter(fsm); |
| 112 | + |
| 113 | + CheckVersion(); |
| 114 | + } |
| 115 | + |
63 | 116 | bool MenuState::Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) |
64 | 117 | { |
| 118 | + if (m_nextState) |
| 119 | + { |
| 120 | + fsm.ChangeState(std::move(m_nextState)); |
| 121 | + return true; |
| 122 | + } |
| 123 | + |
65 | 124 | if (m_autoConnect) |
66 | 125 | { |
67 | 126 | OnConnectPressed(); |
68 | 127 | m_autoConnect = false; |
69 | 128 | } |
70 | 129 |
|
| 130 | + if (m_webService) |
| 131 | + m_webService->Poll(); |
| 132 | + |
71 | 133 | return true; |
72 | 134 | } |
73 | 135 |
|
| 136 | + void MenuState::CheckVersion() |
| 137 | + { |
| 138 | + if (!m_webService) |
| 139 | + m_webService = Nz::Network::Instance()->InstantiateWebService(); |
| 140 | + |
| 141 | + std::string versionUrl = fmt::format("http://tsom-api.digitalpulse.software/game_version?platform={}", BuildConfig); |
| 142 | + |
| 143 | + m_updateLayout->Hide(); |
| 144 | + m_newVersionInfo.reset(); |
| 145 | + |
| 146 | + auto request = m_webService->CreateGetRequest(versionUrl, [this](Nz::WebRequestResult&& result) |
| 147 | + { |
| 148 | + if (!result.HasSucceeded() || result.GetStatusCode() != 200) |
| 149 | + { |
| 150 | + fmt::print(fg(fmt::color::red), "failed to get version update\n"); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + semver::version newAssetVersion; |
| 155 | + semver::version newGameVersion; |
| 156 | + UpdateInfo::DownloadInfo assetInfo; |
| 157 | + UpdateInfo::DownloadInfo gameBinariesInfo; |
| 158 | + UpdateInfo::DownloadInfo updaterInfo; |
| 159 | + |
| 160 | + try |
| 161 | + { |
| 162 | + nlohmann::json json = nlohmann::json::parse(result.GetBody()); |
| 163 | + |
| 164 | + newAssetVersion = json["assets_version"]; |
| 165 | + newGameVersion = json["version"]; |
| 166 | + assetInfo = json["assets"]; |
| 167 | + gameBinariesInfo = json["binaries"]; |
| 168 | + updaterInfo = json["updater"]; |
| 169 | + } |
| 170 | + catch (const std::exception& e) |
| 171 | + { |
| 172 | + fmt::print(fg(fmt::color::red), "failed to parse version data: {}\n", e.what()); |
| 173 | + return; |
| 174 | + } |
| 175 | + |
| 176 | + semver::version currentGameVersion(GameMajorVersion, GameMinorVersion, GamePatchVersion); |
| 177 | + |
| 178 | + if (newGameVersion > currentGameVersion) |
| 179 | + { |
| 180 | + m_newVersionInfo.emplace(UpdateInfo{ |
| 181 | + .assets = std::move(assetInfo), |
| 182 | + .version = newGameVersion.to_string(), |
| 183 | + .binaries = std::move(gameBinariesInfo), |
| 184 | + .updater = std::move(updaterInfo) |
| 185 | + }); |
| 186 | + |
| 187 | + fmt::print(fg(fmt::color::yellow), "new version available: {}\n", m_newVersionInfo->version); |
| 188 | + m_updateButton->UpdateText(Nz::SimpleTextDrawer::Draw("Update game to " + m_newVersionInfo->version, 18, Nz::TextStyle_Regular, Nz::Color::sRGBToLinear(Nz::Color(0.13f)))); |
| 189 | + m_updateButton->SetMaximumWidth(m_updateButton->GetPreferredWidth()); |
| 190 | + |
| 191 | + m_updateLayout->Show(); |
| 192 | + } |
| 193 | + }); |
| 194 | + |
| 195 | + request->SetServiceName("TSOM Version Check"); |
| 196 | + |
| 197 | + m_webService->QueueRequest(std::move(request)); |
| 198 | + } |
| 199 | + |
74 | 200 | void MenuState::LayoutWidgets(const Nz::Vector2f& newSize) |
75 | 201 | { |
76 | 202 | m_layout->Resize({ newSize.x * 0.2f, m_layout->GetPreferredHeight() }); |
77 | 203 | m_layout->CenterHorizontal(); |
78 | 204 | m_layout->SetPosition(m_layout->GetPosition().x, newSize.y * 0.2f - m_layout->GetSize().y / 2.f); |
| 205 | + |
| 206 | + m_updateLayout->Resize({ std::max(m_updateLabel->GetPreferredWidth(), m_updateButton->GetPreferredWidth()), m_updateLabel->GetPreferredHeight() * 2.f + m_updateButton->GetPreferredHeight() }); |
| 207 | + m_updateLayout->SetPosition(newSize * Nz::Vector2f(0.9f, 0.1f) - m_updateButton->GetSize() * 0.5f); |
79 | 208 | } |
80 | 209 |
|
81 | 210 | void MenuState::OnConnectPressed() |
@@ -108,4 +237,13 @@ namespace tsom |
108 | 237 | if (auto connectionState = m_connectionState.lock()) |
109 | 238 | connectionState->Connect(serverAddress, m_loginArea->GetText(), shared_from_this()); |
110 | 239 | } |
| 240 | + |
| 241 | + void MenuState::OnUpdatePressed() |
| 242 | + { |
| 243 | + if (!m_newVersionInfo) |
| 244 | + return; |
| 245 | + |
| 246 | + m_nextState = std::make_shared<UpdateState>(GetStateDataPtr(), shared_from_this(), m_webService, std::move(*m_newVersionInfo)); |
| 247 | + m_newVersionInfo.reset(); |
| 248 | + } |
111 | 249 | } |
0 commit comments