Skip to content

Commit 899c1eb

Browse files
authored
fix: player lose items with wrong loading offline informations (#2919)
When the player is offline, in some scenarios the empty items would load and the items would be overlapped with the empty table of the offline player. Resolves #2917 Related to: #2910
1 parent 04fa248 commit 899c1eb

File tree

6 files changed

+53
-50
lines changed

6 files changed

+53
-50
lines changed

src/creatures/players/cyclopedia/player_badge.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ bool PlayerBadge::loyalty(uint8_t amount) {
116116
}
117117

118118
bool PlayerBadge::accountAllLevel(uint8_t amount) {
119-
const auto &players = g_game().getPlayersByAccount(m_player.getAccount(), true);
119+
auto players = g_game().getPlayersByAccount(m_player.getAccount(), true);
120120
uint16_t total = std::accumulate(players.begin(), players.end(), 0, [](uint16_t sum, const std::shared_ptr<Player> &player) {
121121
return sum + player->getLevel();
122122
});

src/game/game.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,8 +1014,9 @@ std::shared_ptr<Player> Game::getPlayerByGUID(const uint32_t &guid, bool allowOf
10141014
if (!allowOffline) {
10151015
return nullptr;
10161016
}
1017+
10171018
std::shared_ptr<Player> tmpPlayer = std::make_shared<Player>(nullptr);
1018-
if (!IOLoginData::loadPlayerById(tmpPlayer, guid)) {
1019+
if (!IOLoginData::loadPlayerById(tmpPlayer, guid, false)) {
10191020
return nullptr;
10201021
}
10211022
tmpPlayer->setOnline(false);
@@ -1029,7 +1030,9 @@ std::string Game::getPlayerNameByGUID(const uint32_t &guid) {
10291030
if (m_playerNameCache.contains(guid)) {
10301031
return m_playerNameCache.at(guid);
10311032
}
1032-
auto player = getPlayerByGUID(guid, true);
1033+
1034+
// This player need read-only purposes and never saved
1035+
const auto &player = getPlayerByGUID(guid, true);
10331036
auto name = player ? player->getName() : "";
10341037
if (!name.empty()) {
10351038
m_playerNameCache[guid] = name;
@@ -1068,9 +1071,10 @@ std::vector<std::shared_ptr<Player>> Game::getPlayersByAccount(std::shared_ptr<A
10681071
if (error != enumToValue(AccountErrors_t::Ok)) {
10691072
return {};
10701073
}
1074+
10711075
std::vector<std::shared_ptr<Player>> ret;
10721076
for (const auto &[name, _] : accountPlayers) {
1073-
auto player = getPlayerByName(name, allowOffline);
1077+
const auto &player = getPlayerByName(name, allowOffline);
10741078
if (player) {
10751079
ret.push_back(player);
10761080
}

src/io/functions/iologindata_load_player.cpp

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ bool IOLoginDataLoad::preLoadPlayer(std::shared_ptr<Player> player, const std::s
105105
return true;
106106
}
107107

108-
bool IOLoginDataLoad::loadPlayerFirst(std::shared_ptr<Player> player, DBResult_ptr result) {
108+
bool IOLoginDataLoad::loadPlayerBasicInfo(std::shared_ptr<Player> player, DBResult_ptr result) {
109109
if (!result || !player) {
110-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
110+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
111111
return false;
112112
}
113113

@@ -184,12 +184,15 @@ bool IOLoginDataLoad::loadPlayerFirst(std::shared_ptr<Player> player, DBResult_p
184184
player->setMaxManaShield(result->getNumber<uint32_t>("max_manashield"));
185185

186186
player->setMarriageSpouse(result->getNumber<int32_t>("marriage_spouse"));
187+
188+
// Experience load
189+
IOLoginDataLoad::loadPlayerExperience(player, result);
187190
return true;
188191
}
189192

190193
void IOLoginDataLoad::loadPlayerExperience(std::shared_ptr<Player> player, DBResult_ptr result) {
191194
if (!result || !player) {
192-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
195+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
193196
return;
194197
}
195198

@@ -212,7 +215,7 @@ void IOLoginDataLoad::loadPlayerExperience(std::shared_ptr<Player> player, DBRes
212215

213216
void IOLoginDataLoad::loadPlayerBlessings(std::shared_ptr<Player> player, DBResult_ptr result) {
214217
if (!result || !player) {
215-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
218+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
216219
return;
217220
}
218221

@@ -223,7 +226,7 @@ void IOLoginDataLoad::loadPlayerBlessings(std::shared_ptr<Player> player, DBResu
223226

224227
void IOLoginDataLoad::loadPlayerConditions(std::shared_ptr<Player> player, DBResult_ptr result) {
225228
if (!result || !player) {
226-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
229+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
227230
return;
228231
}
229232

@@ -243,13 +246,13 @@ void IOLoginDataLoad::loadPlayerConditions(std::shared_ptr<Player> player, DBRes
243246

244247
void IOLoginDataLoad::loadPlayerDefaultOutfit(std::shared_ptr<Player> player, DBResult_ptr result) {
245248
if (!result || !player) {
246-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
249+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
247250
return;
248251
}
249252

250253
player->defaultOutfit.lookType = result->getNumber<uint16_t>("looktype");
251254
if (g_configManager().getBoolean(WARN_UNSAFE_SCRIPTS, __FUNCTION__) && player->defaultOutfit.lookType != 0 && !g_game().isLookTypeRegistered(player->defaultOutfit.lookType)) {
252-
g_logger().warn("[IOLoginData::loadPlayer] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", player->defaultOutfit.lookType);
255+
g_logger().warn("[{}] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", __FUNCTION__, player->defaultOutfit.lookType);
253256
return;
254257
}
255258

@@ -265,7 +268,7 @@ void IOLoginDataLoad::loadPlayerDefaultOutfit(std::shared_ptr<Player> player, DB
265268
player->defaultOutfit.lookFamiliarsType = result->getNumber<uint16_t>("lookfamiliarstype");
266269

267270
if (g_configManager().getBoolean(WARN_UNSAFE_SCRIPTS, __FUNCTION__) && player->defaultOutfit.lookFamiliarsType != 0 && !g_game().isLookTypeRegistered(player->defaultOutfit.lookFamiliarsType)) {
268-
g_logger().warn("[IOLoginData::loadPlayer] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", player->defaultOutfit.lookFamiliarsType);
271+
g_logger().warn("[{}] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", __FUNCTION__, player->defaultOutfit.lookFamiliarsType);
269272
return;
270273
}
271274

@@ -274,7 +277,7 @@ void IOLoginDataLoad::loadPlayerDefaultOutfit(std::shared_ptr<Player> player, DB
274277

275278
void IOLoginDataLoad::loadPlayerSkullSystem(std::shared_ptr<Player> player, DBResult_ptr result) {
276279
if (!result || !player) {
277-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
280+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
278281
return;
279282
}
280283

@@ -296,7 +299,7 @@ void IOLoginDataLoad::loadPlayerSkullSystem(std::shared_ptr<Player> player, DBRe
296299

297300
void IOLoginDataLoad::loadPlayerSkill(std::shared_ptr<Player> player, DBResult_ptr result) {
298301
if (!result || !player) {
299-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
302+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
300303
return;
301304
}
302305

@@ -318,7 +321,7 @@ void IOLoginDataLoad::loadPlayerSkill(std::shared_ptr<Player> player, DBResult_p
318321

319322
void IOLoginDataLoad::loadPlayerKills(std::shared_ptr<Player> player, DBResult_ptr result) {
320323
if (!result || !player) {
321-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
324+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
322325
return;
323326
}
324327

@@ -337,7 +340,7 @@ void IOLoginDataLoad::loadPlayerKills(std::shared_ptr<Player> player, DBResult_p
337340

338341
void IOLoginDataLoad::loadPlayerGuild(std::shared_ptr<Player> player, DBResult_ptr result) {
339342
if (!result || !player) {
340-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
343+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
341344
return;
342345
}
343346

@@ -387,7 +390,7 @@ void IOLoginDataLoad::loadPlayerGuild(std::shared_ptr<Player> player, DBResult_p
387390

388391
void IOLoginDataLoad::loadPlayerStashItems(std::shared_ptr<Player> player, DBResult_ptr result) {
389392
if (!result || !player) {
390-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
393+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
391394
return;
392395
}
393396

@@ -403,7 +406,7 @@ void IOLoginDataLoad::loadPlayerStashItems(std::shared_ptr<Player> player, DBRes
403406

404407
void IOLoginDataLoad::loadPlayerBestiaryCharms(std::shared_ptr<Player> player, DBResult_ptr result) {
405408
if (!result || !player) {
406-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
409+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
407410
return;
408411
}
409412

@@ -456,7 +459,7 @@ void IOLoginDataLoad::loadPlayerBestiaryCharms(std::shared_ptr<Player> player, D
456459

457460
void IOLoginDataLoad::loadPlayerInstantSpellList(std::shared_ptr<Player> player, DBResult_ptr result) {
458461
if (!player) {
459-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
462+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
460463
return;
461464
}
462465

@@ -472,7 +475,7 @@ void IOLoginDataLoad::loadPlayerInstantSpellList(std::shared_ptr<Player> player,
472475

473476
void IOLoginDataLoad::loadPlayerInventoryItems(std::shared_ptr<Player> player, DBResult_ptr result) {
474477
if (!result || !player) {
475-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
478+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
476479
return;
477480
}
478481

@@ -554,7 +557,7 @@ void IOLoginDataLoad::loadPlayerInventoryItems(std::shared_ptr<Player> player, D
554557

555558
void IOLoginDataLoad::loadPlayerStoreInbox(std::shared_ptr<Player> player) {
556559
if (!player) {
557-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
560+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
558561
return;
559562
}
560563

@@ -565,7 +568,7 @@ void IOLoginDataLoad::loadPlayerStoreInbox(std::shared_ptr<Player> player) {
565568

566569
void IOLoginDataLoad::loadRewardItems(std::shared_ptr<Player> player) {
567570
if (!player) {
568-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
571+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
569572
return;
570573
}
571574

@@ -583,7 +586,7 @@ void IOLoginDataLoad::loadRewardItems(std::shared_ptr<Player> player) {
583586

584587
void IOLoginDataLoad::loadPlayerDepotItems(std::shared_ptr<Player> player, DBResult_ptr result) {
585588
if (!result || !player) {
586-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
589+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
587590
return;
588591
}
589592

@@ -622,7 +625,7 @@ void IOLoginDataLoad::loadPlayerDepotItems(std::shared_ptr<Player> player, DBRes
622625

623626
void IOLoginDataLoad::loadPlayerInboxItems(std::shared_ptr<Player> player, DBResult_ptr result) {
624627
if (!result || !player) {
625-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
628+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
626629
return;
627630
}
628631

@@ -658,7 +661,7 @@ void IOLoginDataLoad::loadPlayerInboxItems(std::shared_ptr<Player> player, DBRes
658661

659662
void IOLoginDataLoad::loadPlayerStorageMap(std::shared_ptr<Player> player, DBResult_ptr result) {
660663
if (!result || !player) {
661-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
664+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
662665
return;
663666
}
664667

@@ -674,7 +677,7 @@ void IOLoginDataLoad::loadPlayerStorageMap(std::shared_ptr<Player> player, DBRes
674677

675678
void IOLoginDataLoad::loadPlayerVip(std::shared_ptr<Player> player, DBResult_ptr result) {
676679
if (!result || !player) {
677-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
680+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
678681
return;
679682
}
680683

@@ -712,7 +715,7 @@ void IOLoginDataLoad::loadPlayerVip(std::shared_ptr<Player> player, DBResult_ptr
712715

713716
void IOLoginDataLoad::loadPlayerPreyClass(std::shared_ptr<Player> player, DBResult_ptr result) {
714717
if (!result || !player) {
715-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
718+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
716719
return;
717720
}
718721

@@ -759,7 +762,7 @@ void IOLoginDataLoad::loadPlayerPreyClass(std::shared_ptr<Player> player, DBResu
759762

760763
void IOLoginDataLoad::loadPlayerTaskHuntingClass(std::shared_ptr<Player> player, DBResult_ptr result) {
761764
if (!result || !player) {
762-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
765+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
763766
return;
764767
}
765768

@@ -809,7 +812,7 @@ void IOLoginDataLoad::loadPlayerTaskHuntingClass(std::shared_ptr<Player> player,
809812

810813
void IOLoginDataLoad::loadPlayerForgeHistory(std::shared_ptr<Player> player, DBResult_ptr result) {
811814
if (!result || !player) {
812-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
815+
g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__);
813816
return;
814817
}
815818

@@ -830,12 +833,12 @@ void IOLoginDataLoad::loadPlayerForgeHistory(std::shared_ptr<Player> player, DBR
830833

831834
void IOLoginDataLoad::loadPlayerBosstiary(std::shared_ptr<Player> player, DBResult_ptr result) {
832835
if (!result) {
833-
g_logger().warn("[IOLoginData::loadPlayer] - Result nullptr: {}", __FUNCTION__);
836+
g_logger().warn("[{}] - Result nullptr", __FUNCTION__);
834837
return;
835838
}
836839

837840
if (!player) {
838-
g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__);
841+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
839842
return;
840843
}
841844

@@ -867,7 +870,7 @@ void IOLoginDataLoad::loadPlayerBosstiary(std::shared_ptr<Player> player, DBResu
867870

868871
void IOLoginDataLoad::bindRewardBag(std::shared_ptr<Player> player, ItemsMap &rewardItemsMap) {
869872
if (!player) {
870-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
873+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
871874
return;
872875
}
873876

@@ -907,7 +910,7 @@ void IOLoginDataLoad::insertItemsIntoRewardBag(const ItemsMap &rewardItemsMap) {
907910

908911
void IOLoginDataLoad::loadPlayerInitializeSystem(std::shared_ptr<Player> player) {
909912
if (!player) {
910-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
913+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
911914
return;
912915
}
913916

@@ -926,7 +929,7 @@ void IOLoginDataLoad::loadPlayerInitializeSystem(std::shared_ptr<Player> player)
926929

927930
void IOLoginDataLoad::loadPlayerUpdateSystem(std::shared_ptr<Player> player) {
928931
if (!player) {
929-
g_logger().warn("[IOLoginData::loadPlayer] - Player nullptr: {}", __FUNCTION__);
932+
g_logger().warn("[{}] - Player nullptr", __FUNCTION__);
930933
return;
931934
}
932935

src/io/functions/iologindata_load_player.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
class IOLoginDataLoad : public IOLoginData {
1515
public:
16-
static bool loadPlayerFirst(std::shared_ptr<Player> player, DBResult_ptr result);
16+
static bool loadPlayerBasicInfo(std::shared_ptr<Player> player, DBResult_ptr result);
1717
static bool preLoadPlayer(std::shared_ptr<Player> player, const std::string &name);
1818
static void loadPlayerExperience(std::shared_ptr<Player> player, DBResult_ptr result);
1919
static void loadPlayerBlessings(std::shared_ptr<Player> player, DBResult_ptr result);

src/io/iologindata.cpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,22 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) {
9494
Database::getInstance().executeQuery(query.str());
9595
}
9696

97-
// The boolean "disableIrrelevantInfo" will deactivate the loading of information that is not relevant to the preload, for example, forge, bosstiary, etc. None of this we need to access if the player is offline
98-
bool IOLoginData::loadPlayerById(std::shared_ptr<Player> player, uint32_t id, bool disableIrrelevantInfo /* = true*/) {
97+
// The boolean "loadBasicInfoOnly" will deactivate the loading of information that is not relevant to the preload, for example, forge, bosstiary, etc. None of this we need to access if the player is offline
98+
bool IOLoginData::loadPlayerById(std::shared_ptr<Player> player, uint32_t id, bool loadBasicInfoOnly /* = true*/) {
9999
Database &db = Database::getInstance();
100100
std::ostringstream query;
101101
query << "SELECT * FROM `players` WHERE `id` = " << id;
102-
return loadPlayer(player, db.storeQuery(query.str()), disableIrrelevantInfo);
102+
return loadPlayer(player, db.storeQuery(query.str()), loadBasicInfoOnly);
103103
}
104104

105-
bool IOLoginData::loadPlayerByName(std::shared_ptr<Player> player, const std::string &name, bool disableIrrelevantInfo /* = true*/) {
105+
bool IOLoginData::loadPlayerByName(std::shared_ptr<Player> player, const std::string &name, bool loadBasicInfoOnly /* = true*/) {
106106
Database &db = Database::getInstance();
107107
std::ostringstream query;
108108
query << "SELECT * FROM `players` WHERE `name` = " << db.escapeString(name);
109-
return loadPlayer(player, db.storeQuery(query.str()), disableIrrelevantInfo);
109+
return loadPlayer(player, db.storeQuery(query.str()), loadBasicInfoOnly);
110110
}
111111

112-
bool IOLoginData::loadPlayer(std::shared_ptr<Player> player, DBResult_ptr result, bool disableIrrelevantInfo /* = false*/) {
112+
bool IOLoginData::loadPlayer(std::shared_ptr<Player> player, DBResult_ptr result, bool loadBasicInfoOnly /* = false*/) {
113113
if (!result || !player) {
114114
std::string nullptrType = !result ? "Result" : "Player";
115115
g_logger().warn("[{}] - {} is nullptr", __FUNCTION__, nullptrType);
@@ -118,12 +118,8 @@ bool IOLoginData::loadPlayer(std::shared_ptr<Player> player, DBResult_ptr result
118118

119119
try {
120120
// First
121-
IOLoginDataLoad::loadPlayerFirst(player, result);
122-
123-
// Experience load
124-
IOLoginDataLoad::loadPlayerExperience(player, result);
125-
126-
if (disableIrrelevantInfo) {
121+
IOLoginDataLoad::loadPlayerBasicInfo(player, result);
122+
if (loadBasicInfoOnly) {
127123
return true;
128124
}
129125

src/io/iologindata.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class IOLoginData {
2020
static bool gameWorldAuthentication(const std::string &accountDescriptor, const std::string &sessionOrPassword, std::string &characterName, uint32_t &accountId, bool oldProcotol, const uint32_t ip);
2121
static uint8_t getAccountType(uint32_t accountId);
2222
static void updateOnlineStatus(uint32_t guid, bool login);
23-
static bool loadPlayerById(std::shared_ptr<Player> player, uint32_t id, bool disableIrrelevantInfo = true);
24-
static bool loadPlayerByName(std::shared_ptr<Player> player, const std::string &name, bool disableIrrelevantInfo = true);
25-
static bool loadPlayer(std::shared_ptr<Player> player, DBResult_ptr result, bool disableIrrelevantInfo = false);
23+
static bool loadPlayerById(std::shared_ptr<Player> player, uint32_t id, bool loadBasicInfoOnly = true);
24+
static bool loadPlayerByName(std::shared_ptr<Player> player, const std::string &name, bool loadBasicInfoOnly = true);
25+
static bool loadPlayer(std::shared_ptr<Player> player, DBResult_ptr result, bool loadBasicInfoOnly = false);
2626
static bool savePlayer(std::shared_ptr<Player> player);
2727
static uint32_t getGuidByName(const std::string &name);
2828
static bool getGuidByNameEx(uint32_t &guid, bool &specialVip, std::string &name);

0 commit comments

Comments
 (0)