Skip to content

Commit 632b4f9

Browse files
committed
Add version check and automatic update
1 parent 86a797e commit 632b4f9

9 files changed

Lines changed: 506 additions & 6 deletions

File tree

include/CommonLib/Version.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace tsom
1717
TSOM_COMMONLIB_API extern std::uint32_t GameMinorVersion;
1818
TSOM_COMMONLIB_API extern std::uint32_t GamePatchVersion;
1919

20+
TSOM_COMMONLIB_API extern std::string_view BuildConfig;
2021
TSOM_COMMONLIB_API extern std::string_view BuildSystem;
2122
TSOM_COMMONLIB_API extern std::string_view BuildBranch;
2223
TSOM_COMMONLIB_API extern std::string_view BuildCommit;
@@ -35,7 +36,6 @@ namespace tsom
3536
minorVersion = (version >> 12) & 0x3FF;
3637
patchVersion = (version >> 0) & 0xFFF;
3738
}
38-
3939
}
4040

4141
#include <CommonLib/Version.inl>

src/Game/States/MenuState.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,47 @@
55
#include <Game/States/MenuState.hpp>
66
#include <Game/States/ConnectionState.hpp>
77
#include <Game/States/GameState.hpp>
8+
#include <Game/States/UpdateState.hpp>
89
#include <CommonLib/GameConstants.hpp>
10+
#include <CommonLib/Version.hpp>
911
#include <Nazara/Core/ApplicationBase.hpp>
1012
#include <Nazara/Core/StateMachine.hpp>
1113
#include <Nazara/Network/Algorithm.hpp>
1214
#include <Nazara/Network/IpAddress.hpp>
15+
#include <Nazara/Network/Network.hpp>
16+
#include <Nazara/Network/WebRequest.hpp>
17+
#include <Nazara/Network/WebService.hpp>
1318
#include <Nazara/Utility/SimpleTextDrawer.hpp>
1419
#include <Nazara/Widgets.hpp>
1520
#include <fmt/color.h>
1621
#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+
}
1749

1850
namespace tsom
1951
{
@@ -52,30 +84,127 @@ namespace tsom
5284

5385
m_connectButton = m_layout->Add<Nz::ButtonWidget>();
5486
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);
5588
m_connectButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*)
5689
{
5790
OnConnectPressed();
5891
});
5992

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+
60106
m_autoConnect = cmdParams.HasFlag("auto-connect");
61107
}
62108

109+
void MenuState::Enter(Nz::StateMachine& fsm)
110+
{
111+
WidgetState::Enter(fsm);
112+
113+
CheckVersion();
114+
}
115+
63116
bool MenuState::Update(Nz::StateMachine& fsm, Nz::Time elapsedTime)
64117
{
118+
if (m_nextState)
119+
{
120+
fsm.ChangeState(std::move(m_nextState));
121+
return true;
122+
}
123+
65124
if (m_autoConnect)
66125
{
67126
OnConnectPressed();
68127
m_autoConnect = false;
69128
}
70129

130+
if (m_webService)
131+
m_webService->Poll();
132+
71133
return true;
72134
}
73135

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+
74200
void MenuState::LayoutWidgets(const Nz::Vector2f& newSize)
75201
{
76202
m_layout->Resize({ newSize.x * 0.2f, m_layout->GetPreferredHeight() });
77203
m_layout->CenterHorizontal();
78204
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);
79208
}
80209

81210
void MenuState::OnConnectPressed()
@@ -108,4 +237,13 @@ namespace tsom
108237
if (auto connectionState = m_connectionState.lock())
109238
connectionState->Connect(serverAddress, m_loginArea->GetText(), shared_from_this());
110239
}
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+
}
111249
}

src/Game/States/MenuState.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
#define TSOM_CLIENT_STATES_MENUSTATE_HPP
99

1010
#include <Game/States/WidgetState.hpp>
11+
#include <Game/States/UpdateInfo.hpp>
12+
#include <string>
1113

1214
namespace Nz
1315
{
1416
class BoxLayout;
1517
class ButtonWidget;
18+
class LabelWidget;
1619
class TextAreaWidget;
20+
class WebService;
1721
}
1822

1923
namespace tsom
@@ -26,15 +30,24 @@ namespace tsom
2630
MenuState(std::shared_ptr<StateData> stateData, std::shared_ptr<ConnectionState> connectionState);
2731
~MenuState() = default;
2832

33+
void Enter(Nz::StateMachine& fsm) override;
2934
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
3035

3136
private:
37+
void CheckVersion();
3238
void LayoutWidgets(const Nz::Vector2f& newSize) override;
3339
void OnConnectPressed();
40+
void OnUpdatePressed();
3441

42+
std::optional<UpdateInfo> m_newVersionInfo;
43+
std::shared_ptr<Nz::State> m_nextState;
44+
std::shared_ptr<Nz::WebService> m_webService;
3545
std::weak_ptr<ConnectionState> m_connectionState;
3646
Nz::BoxLayout* m_layout;
47+
Nz::BoxLayout* m_updateLayout;
3748
Nz::ButtonWidget* m_connectButton;
49+
Nz::ButtonWidget* m_updateButton;
50+
Nz::LabelWidget* m_updateLabel;
3851
Nz::TextAreaWidget* m_serverAddressArea;
3952
Nz::TextAreaWidget* m_loginArea;
4053
bool m_autoConnect;

src/Game/States/UpdateInfo.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in Config.hpp
4+
5+
#pragma once
6+
7+
#ifndef TSOM_CLIENT_STATES_UPDATEINFO_HPP
8+
#define TSOM_CLIENT_STATES_UPDATEINFO_HPP
9+
10+
#include <optional>
11+
#include <string>
12+
13+
namespace tsom
14+
{
15+
struct UpdateInfo
16+
{
17+
struct DownloadInfo
18+
{
19+
std::string downloadUrl;
20+
std::string sha256;
21+
Nz::UInt64 size;
22+
};
23+
24+
std::optional<DownloadInfo> assets;
25+
std::string version;
26+
DownloadInfo binaries;
27+
DownloadInfo updater;
28+
};
29+
}
30+
31+
#endif // TSOM_CLIENT_STATES_UPDATEINFO_HPP

0 commit comments

Comments
 (0)