From 549c611ea7d0556d305ca4f66d797f6cdaca84f9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Feb 2024 12:13:21 +1100 Subject: [PATCH 1/6] * playfab add more logging --- src/playfab.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/playfab.cpp b/src/playfab.cpp index 1a54cd173..24b834bf4 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -17,8 +17,10 @@ #endif PlayfabUser_t playfabUser; +int loginFailures = 1; void PlayfabUser_t::OnLoginSuccess(const PlayFab::ClientModels::LoginResult& result, void* customData) { + loginFailures = 1; logInfo("Logged in successfully"); playfabUser.loggingIn = false; playfabUser.bLoggedIn = true; @@ -72,6 +74,8 @@ void PlayfabUser_t::OnLoginSuccess(const PlayFab::ClientModels::LoginResult& res void PlayfabUser_t::OnLoginFail(const PlayFab::PlayFabError& error, void* customData) { logError("Failed to login: %s | %s", error.ErrorName.c_str(), error.ErrorMessage.c_str()); + logError(error.GenerateErrorReport().c_str()); + playfabUser.loggingIn = false; playfabUser.bLoggedIn = false; playfabUser.errorLogin = true; @@ -98,8 +102,9 @@ void PlayfabUser_t::loginEpic() request.IdToken = EOS.getAuthToken(); if ( EOS.getAuthToken() == "" ) { - playfabUser.authenticationRefresh = TICKS_PER_SECOND * 5; + playfabUser.authenticationRefresh = TICKS_PER_SECOND * 5 * loginFailures; playfabUser.errorLogin = true; + loginFailures = std::min(20, loginFailures + 3); return; } playfabUser.loggingIn = true; @@ -113,6 +118,13 @@ void PlayfabUser_t::loginSteam() PlayFab::ClientModels::LoginWithSteamRequest request; request.CreateAccount = true; request.SteamTicket = SteamClientRequestAuthTicket(); + if ( request.SteamTicket == "" ) + { + playfabUser.authenticationRefresh = TICKS_PER_SECOND * 5 * loginFailures; + playfabUser.errorLogin = true; + loginFailures = std::min(20, loginFailures + 3); + return; + } playfabUser.loggingIn = true; PlayFab::PlayFabClientAPI::LoginWithSteam(request, OnLoginSuccess, OnLoginFail); #endif @@ -125,6 +137,17 @@ void PlayfabUser_t::OnCloudScriptExecute(const PlayFab::ClientModels::ExecuteClo void PlayfabUser_t::OnCloudScriptFailure(const PlayFab::PlayFabError& error, void* customData) { + /*if ( error.ErrorCode == PlayFab::PlayFabErrorCode::PlayFabErrorEntityTokenExpired ) + { + // i think this response is from the server, so dont need to re-log + if ( playfabUser.bLoggedIn ) + { + if ( playfabUser.authenticationRefresh > TICKS_PER_SECOND * 5 ) + { + playfabUser.authenticationRefresh = TICKS_PER_SECOND * 5; + } + } + }*/ logError("Update failure: %s", error.ErrorMessage.c_str()); } From 770822010064efedb0da560bf90588cb1f8842a7 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Feb 2024 12:13:52 +1100 Subject: [PATCH 2/6] * fix hash check failing loading scores --- src/scores.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scores.cpp b/src/scores.cpp index 5ceb93c48..e7c2e643b 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -5594,10 +5594,10 @@ void SaveGameInfo::computeHash(const int playernum, Uint32& hash) hash += (Uint32)((Uint32)k << (shift % 32)); ++shift; } - hash += (Uint32)((Uint32)conductPenniless << (shift % 32)); ++shift; - hash += (Uint32)((Uint32)conductFoodless << (shift % 32)); ++shift; - hash += (Uint32)((Uint32)conductVegetarian << (shift % 32)); ++shift; - hash += (Uint32)((Uint32)conductIlliterate << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)player.conductPenniless << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)player.conductFoodless << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)player.conductVegetarian << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)player.conductIlliterate << (shift % 32)); ++shift; for ( int i = 0; i < NUM_CONDUCT_CHALLENGES; ++i ) { hash += (Uint32)((Uint32)player.additionalConducts[i] << (shift % 32)); ++shift; From c3c4c1c58d838db79d309735775d14f290bf7b28 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Feb 2024 12:28:35 +1100 Subject: [PATCH 3/6] * correct mimic moveaside, last param not needed to be false --- src/actmonster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index c340f598d..ac1d696f4 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -12448,7 +12448,7 @@ void mimicResetIdle(Entity* my) int tx = pair.first; int ty = pair.second; - if ( checkObstacle((tx << 4) + 8, (ty << 4) + 8, my, nullptr, false) ) + if ( checkObstacle((tx << 4) + 8, (ty << 4) + 8, my, nullptr) ) { if ( tx == x + 1 || tx == x - 1 ) { From c98b039157d333b00fd406c7bf0f0f088598da77 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 29 Feb 2024 12:28:55 +1100 Subject: [PATCH 4/6] * revise online score format - discard name from blob so it doesn't get hashed * fix other player torches showing in leaderboard preview --- src/entity.cpp | 1 + src/playfab.cpp | 16 ++++++++++++++-- src/playfab.hpp | 4 +++- src/scores.cpp | 2 +- src/ui/GameUI.cpp | 8 ++++++++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 79a32578a..d0cb79367 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -20557,6 +20557,7 @@ void Entity::handleHumanoidShieldLimb(Entity* shieldLimb, Entity* shieldArmLimb) { flameEntity->flags[GENIUS] = true; flameEntity->setUID(-4); + flameEntity->skill[1] = player + 1; } else { diff --git a/src/playfab.cpp b/src/playfab.cpp index ad110d997..27eee14ce 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -1043,6 +1043,7 @@ void PlayfabUser_t::PostScoreHandler_t::ScoreUpdate_t::post() request.GeneratePlayStreamEvent = false; request.CustomTags["default"] = "1"; request.CustomTags["hash"] = hash; + request.CustomTags["name"] = name; request.FunctionParameter = json; PlayFab::PlayFabCloudScriptAPI::ExecuteFunction(request, OnFunctionExecute, OnCloudScriptFailure, (void*)(intptr_t)(sequence)); @@ -1158,6 +1159,10 @@ unsigned long djb2Hash2(char* str) void PlayfabUser_t::postScore(const int player) { + if ( player < 0 || player >= MAPLAYERS ) + { + return; + } #ifdef NDEBUG { if ( conductGameChallenges[CONDUCT_CHEATS_ENABLED] @@ -1197,7 +1202,8 @@ void PlayfabUser_t::postScore(const int player) } postScoreHandler.sessionsPosted.insert(info.hash); - postScoreHandler.queue.push_back(PostScoreHandler_t::ScoreUpdate_t(scorestring, std::to_string(hash))); + postScoreHandler.queue.push_back(PostScoreHandler_t::ScoreUpdate_t(scorestring, std::to_string(hash), + info.players[player].stats.name)); auto& entry = postScoreHandler.queue.back(); entry.inprogress = false; entry.saveToFile(); @@ -1237,6 +1243,7 @@ bool PlayfabUser_t::PostScoreHandler_t::ScoreUpdate_t::saveToFile() d.AddMember("version", rapidjson::Value(1), d.GetAllocator()); d.AddMember("hash", rapidjson::Value(hash.c_str(), d.GetAllocator()), d.GetAllocator()); + d.AddMember("name", rapidjson::Value(hash.c_str(), d.GetAllocator()), d.GetAllocator()); d.AddMember("score", rapidjson::Value(score.c_str(), d.GetAllocator()), d.GetAllocator()); File* fp = FileIO::open(outputPath.c_str(), "wb"); @@ -1303,9 +1310,14 @@ void PlayfabUser_t::PostScoreHandler_t::readFromFiles() int version = d["version"].GetInt(); std::string score = d["score"].GetString(); std::string hashStr = d["hash"].GetString(); + std::string name = ""; + if ( d.HasMember("name") ) + { + name = d["name"].GetString(); + } Uint32 hash = std::stoul(hashStr); - queue.push_back(ScoreUpdate_t(score, hashStr)); + queue.push_back(ScoreUpdate_t(score, hashStr, name)); queue.back().writtenToFile = inputPath; if ( (Uint32)djb2Hash2(const_cast(score.c_str())) != hash ) diff --git a/src/playfab.hpp b/src/playfab.hpp index bbac9554f..95f03b96d 100644 --- a/src/playfab.hpp +++ b/src/playfab.hpp @@ -93,6 +93,7 @@ class PlayfabUser_t { std::string hash = ""; std::string score = ""; + std::string name = ""; PlayFab::PlayFabErrorCode code = PlayFab::PlayFabErrorCode::PlayFabErrorUnknownError; bool inprogress = false; int sequence = 0; @@ -102,10 +103,11 @@ class PlayfabUser_t bool expired = false; std::string writtenToFile = ""; - ScoreUpdate_t(std::string _score, std::string _hash) + ScoreUpdate_t(std::string _score, std::string _hash, std::string _name) { hash = _hash; score = _score; + name = _name; sequence = sequenceIDs; creationTick = ticks; ++sequenceIDs; diff --git a/src/scores.cpp b/src/scores.cpp index e7c2e643b..b43642ca6 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -6293,7 +6293,7 @@ std::string SaveGameInfo::serializeToOnlineHiscore(const int playernum, const in { rapidjson::Value statsObj(rapidjson::kObjectType); - statsObj.AddMember("name", rapidjson::Value(myStats.name.c_str(), d.GetAllocator()), d.GetAllocator()); + //statsObj.AddMember("name", rapidjson::Value(myStats.name.c_str(), d.GetAllocator()), d.GetAllocator()); statsObj.AddMember("MAXHP", myStats.maxHP, d.GetAllocator()); statsObj.AddMember("MAXMP", myStats.maxMP, d.GetAllocator()); statsObj.AddMember("STR", myStats.STR, d.GetAllocator()); diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index f2c7a9172..4b1232927 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -21512,6 +21512,10 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset Entity* entity = (Entity*)node->element; if ( (Sint32)entity->getUID() == -4 ) // torch sprites { + if ( (entity->skill[1] - 1) != player ) + { + continue; + } bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } glDrawSprite(&view, entity, REALCOLORS); @@ -21530,6 +21534,10 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset { if ( (Sint32)entity->getUID() == -4 ) // torch sprites { + if ( (entity->skill[1] - 1) != player ) + { + continue; + } bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } glDrawSprite(&view, entity, REALCOLORS); From d59efda675685593e7d06016e86749475d42ae88 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 1 Mar 2024 10:59:42 +1100 Subject: [PATCH 5/6] * fix score hash not being cross platform --- src/playfab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playfab.cpp b/src/playfab.cpp index 27eee14ce..ab42bbc7b 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -1191,7 +1191,7 @@ void PlayfabUser_t::postScore(const int player) } std::string scorestring = info.serializeToOnlineHiscore(player, victory); - auto hash = djb2Hash2(const_cast(scorestring.c_str())); + Uint32 hash = djb2Hash2(const_cast(scorestring.c_str())); if ( postScoreHandler.sessionsPosted.find(info.hash) != postScoreHandler.sessionsPosted.end() ) { From 4f1e267c77f1cdbe30100e5abd5bd554243bdc8b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 1 Mar 2024 13:35:44 +1100 Subject: [PATCH 6/6] * new achievements * fix conjure skel items getting eaten --- src/actboulder.cpp | 13 +++++ src/actflame.cpp | 1 + src/actmonster.cpp | 19 +++++++ src/charclass.cpp | 6 +++ src/entity.cpp | 60 +++++++++++++++++++++-- src/game.cpp | 9 ++++ src/main.cpp | 8 ++- src/main.hpp | 2 +- src/menu.cpp | 5 ++ src/playfab.cpp | 67 +++++++++++++++++++++++++ src/scores.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++++- src/scores.hpp | 27 ++++++++-- src/steam.cpp | 38 ++++++++++++++ 13 files changed, 364 insertions(+), 11 deletions(-) diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 3fa74bc5c..2bd0d4bd2 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -260,6 +260,11 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit bool cursedItemIsBuff = shouldInvertEquipmentBeatitude(stats); if ( stats->helmet->beatitude >= 0 || cursedItemIsBuff ) { + if ( stats->HP <= damage ) + { + // saved us + steamAchievementEntity(entity, "BARONY_ACH_CRUMPLE_ZONES"); + } damage = 0; } stats->helmet->status = BROKEN; @@ -279,6 +284,14 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit mult += 0.25 * abs(stats->helmet->beatitude); } + if ( stats->HP <= damage ) + { + // saved us + if ( stats->HP > (damage * mult) ) + { + steamAchievementEntity(entity, "BARONY_ACH_CRUMPLE_ZONES"); + } + } damage *= mult; if ( stats->helmet->status > BROKEN ) { diff --git a/src/actflame.cpp b/src/actflame.cpp index 568a57c16..1d6da76f2 100644 --- a/src/actflame.cpp +++ b/src/actflame.cpp @@ -26,6 +26,7 @@ -------------------------------------------------------------------------------*/ #define FLAME_LIFE my->skill[0] +#define FLAME_PLAYER my->skill[1] #define FLAME_VELX my->vel_x #define FLAME_VELY my->vel_y #define FLAME_VELZ my->vel_z diff --git a/src/actmonster.cpp b/src/actmonster.cpp index ac1d696f4..c65cb8507 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1863,6 +1863,20 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], steamAchievementClient(monsterclicked, "BARONY_ACH_YOUNG_BLOOD"); } } + if ( stats[monsterclicked]->sex != myStats->sex ) + { + if ( stats[monsterclicked]->mask + && (stats[monsterclicked]->mask->type == MASK_SPOOKY + || stats[monsterclicked]->mask->type == MASK_GOLDEN + || stats[monsterclicked]->mask->type == MASK_ARTIFACT_VISOR + || stats[monsterclicked]->mask->type == MASK_STEEL_VISOR + || stats[monsterclicked]->mask->type == MASK_CRYSTAL_VISOR + || stats[monsterclicked]->mask->type == MASK_PLAGUE + )) + { + steamAchievementClient(monsterclicked, "BARONY_ACH_SSSMOKIN"); + } + } if ( myStats->type == HUMAN && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->appearance == 0 && stats[monsterclicked]->playerRace == RACE_AUTOMATON ) { @@ -1954,6 +1968,11 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], { free(armor); } + + if ( players[monsterclicked] ) + { + steamAchievementEntity(players[monsterclicked]->entity, "BARONY_ACH_MON_PETIT"); + } } return true; diff --git a/src/charclass.cpp b/src/charclass.cpp index 31aed0234..abc6d9c10 100644 --- a/src/charclass.cpp +++ b/src/charclass.cpp @@ -2932,6 +2932,12 @@ void initClass(const int player) Item* item = static_cast(node->element); if ( item ) { + if ( items[item->type].item_slot == EQUIPPABLE_IN_SLOT_HELM + || items[item->type].item_slot == EQUIPPABLE_IN_SLOT_MASK ) + { + assert(achievementObserver.playerAchievements->startingClassItems.find(item->type) + != achievementObserver.playerAchievements->startingClassItems.end()); + } if ( players[player]->paperDoll.enabled && itemIsEquipped(item, player) && item->type != SPELL_ITEM ) { continue; diff --git a/src/entity.cpp b/src/entity.cpp index d0cb79367..4da110de6 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -3591,6 +3591,8 @@ void Entity::handleEffects(Stat* myStats) { if ( ticks % (HEAL_TIME) == 0 ) { + steamAchievementEntity(this, "BARONY_ACH_SMOKIN"); + int damage = 1 + local_rng.rand() % 3; this->modHP(-damage); if ( myStats->HP <= 0 ) @@ -6681,6 +6683,7 @@ void Entity::attack(int pose, int charge, Entity* target) if ( myStats->HP <= 0 && oldHP > 0 ) { killer->awardXP(this, true, true); + steamAchievementEntity(killer, "BARONY_ACH_LOCKJAW"); } updateEnemyBar(killer, this, getMonsterLocalizedName(myStats->type).c_str(), myStats->HP, myStats->MAXHP, true, @@ -9316,7 +9319,10 @@ void Entity::attack(int pose, int charge, Entity* target) { Item* armor = nullptr; int armornum = 0; - if ( behavior == &actPlayer ) + if ( behavior == &actPlayer + || (hit.entity->behavior == &actMonster + && ((hit.entity->monsterAllySummonRank != 0 && hitstats->type == SKELETON) + || hit.entity->monsterIsTinkeringCreation())) ) { armor = nullptr; } @@ -9349,6 +9355,10 @@ void Entity::attack(int pose, int charge, Entity* target) { armor->count--; } + if ( hit.entity->behavior == &actPlayer && playerhit >= 0 ) + { + steamStatisticUpdateClient(playerhit, STEAM_STAT_I_NEEDED_THAT, STEAM_STAT_INT, 1); + } messagePlayer(playerhit, MESSAGE_COMBAT, Language::get(6085), armor->getName()); Item* stolenArmor = newItem(armor->type, armor->status, armor->beatitude, qty, armor->appearance, armor->identified, &myStats->inventory); stolenArmor->ownerUid = hit.entity->getUID(); @@ -9438,7 +9448,10 @@ void Entity::attack(int pose, int charge, Entity* target) { Item* armor = nullptr; int armornum = 0; - if ( behavior == &actPlayer ) + if ( behavior == &actPlayer + || (hit.entity->behavior == &actMonster + && ( (hit.entity->monsterAllySummonRank != 0 && hitstats->type == SKELETON) + || hit.entity->monsterIsTinkeringCreation())) ) { armor = nullptr; } @@ -11896,6 +11909,11 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( inspiration ) { gain *= inspirationMult; + if ( ((followerStats->EXP + gain) >= 100) && ((followerStats->EXP + (xpGain * numshares)) < 100) ) + { + // inspiration caused us to level + steamAchievementEntity(this, "BARONY_ACH_BY_EXAMPLE"); + } } followerStats->EXP += gain; } @@ -11905,6 +11923,11 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( inspiration ) { gain *= inspirationMult; + if ( ((followerStats->EXP + gain) >= 100) && ((followerStats->EXP + xpGain) < 100) ) + { + // inspiration caused us to level + steamAchievementEntity(this, "BARONY_ACH_BY_EXAMPLE"); + } } followerStats->EXP += gain; } @@ -11927,6 +11950,17 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( inspiration ) { gain *= inspirationMult; + if ( ((destStats->EXP + gain) >= 100) && ((destStats->EXP + xpGain) < 100) ) + { + // inspiration caused us to level + if ( behavior == &actMonster ) + { + if ( auto leader = monsterAllyGetPlayerLeader() ) + { + steamAchievementEntity(leader, "BARONY_ACH_BY_EXAMPLE"); + } + } + } } destStats->EXP += gain; } @@ -11953,6 +11987,10 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( src->behavior == &actPlayer && this->behavior == &actMonster ) { achievementObserver.updateGlobalStat(getIndexForDeathType(destStats->type)); + if ( destStats->type == MIMIC ) + { + steamAchievementClient(src->skill[2], "BARONY_ACH_ETERNAL_REWARD"); + } } else if ( src->behavior == &actMonster && this->behavior == &actPlayer ) { @@ -11980,6 +12018,22 @@ void Entity::awardXP(Entity* src, bool share, bool root) { achievementObserver.updateGlobalStat(STEAM_GSTAT_SHOPKEEPERS_SLAIN); } + + if ( srcStats->type == LICH || srcStats->type == LICH_FIRE || srcStats->type == LICH_ICE + || srcStats->type == DEVIL ) + { + if ( gameModeManager.currentSession.challengeRun.isActive() ) + { + if ( gameModeManager.currentSession.challengeRun.classnum >= 0 + || gameModeManager.currentSession.challengeRun.race >= 0 ) + { + for ( int c = 0; c < MAXPLAYERS; c++ ) + { + steamAchievementClient(c, "BARONY_ACH_BLOOM_PLANTED"); + } + } + } + } } } @@ -15881,7 +15935,7 @@ bool Entity::setEffect(int effect, bool value, int duration, bool updateClients, case EFF_CONFUSED: if ( myStats->type == LICH || myStats->type == DEVIL || myStats->type == LICH_FIRE || myStats->type == LICH_ICE - || myStats->type == MINOTAUR ) + || myStats->type == MINOTAUR || myStats->type == MIMIC ) { return false; } diff --git a/src/game.cpp b/src/game.cpp index 7694e55b0..97cc80579 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1485,6 +1485,12 @@ void gameLogic(void) steamAchievementClient(c, "BARONY_ACH_GILDED"); } + if ( stats[c]->helmet && stats[c]->helmet->type == HAT_WOLF_HOOD + && stats[c]->helmet->beatitude > 0 ) + { + steamAchievementClient(c, "BARONY_ACH_PET_DA_DOG"); + } + if ( stats[c]->helmet && stats[c]->helmet->type == ARTIFACT_HELM && stats[c]->breastplate && stats[c]->breastplate->type == ARTIFACT_BREASTPIECE && stats[c]->gloves && stats[c]->gloves->type == ARTIFACT_GLOVES @@ -1628,6 +1634,7 @@ void gameLogic(void) gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); + achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); //if( TICKS_PER_SECOND ) //generatePathMaps(); @@ -2994,11 +3001,13 @@ void gameLogic(void) updateGameplayStatisticsInMainLoop(); } + for ( int i = 0; i < MAXPLAYERS; ++i ) { gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); + achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); // ask for entity delete update if ( ticks % 4 == 0 && list_Size(map.entities) ) diff --git a/src/main.cpp b/src/main.cpp index ded20ebab..7e3bb9608 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -289,7 +289,13 @@ SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS] = { 46, STEAM_STAT_INT, "STAT_EXTRA_CREDIT_LVLS" }, { 47, STEAM_STAT_INT, "STAT_DIPLOMA" }, { 48, STEAM_STAT_INT, "STAT_DIPLOMA_LVLS" }, - { 49, STEAM_STAT_INT, "STAT_TUTORIAL_ENTERED" } + { 49, STEAM_STAT_INT, "STAT_TUTORIAL_ENTERED" }, + { 50, STEAM_STAT_INT, "STAT_I_NEEDED_THAT" }, + { 51, STEAM_STAT_INT, "STAT_DAPPER_1"}, + { 52, STEAM_STAT_INT, "STAT_DAPPER_2"}, + { 53, STEAM_STAT_INT, "STAT_DAPPER_3"}, + { 54, STEAM_STAT_INT, "STAT_DAPPER"}, + { 55, STEAM_STAT_INT, "STAT_DUNGEONSEED" } }; SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS] = diff --git a/src/main.hpp b/src/main.hpp index 148059fbd..7be5c0eb3 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -898,7 +898,7 @@ extern bool initialized; //So that messagePlayer doesn't explode before the game void GO_SwapBuffers(SDL_Window* screen); -static const int NUM_STEAM_STATISTICS = 49; +static const int NUM_STEAM_STATISTICS = 55; extern SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS]; static const int NUM_GLOBAL_STEAM_STATISTICS = 66; extern SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS]; diff --git a/src/menu.cpp b/src/menu.cpp index 680a2c367..c9d4c276e 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9685,6 +9685,11 @@ void doEndgame(bool saveHighscore) { } } + if ( gameModeManager.currentSession.challengeRun.isActive() ) + { + steamAchievement("BARONY_ACH_REAP_SOW"); + } + if ( victory == 1 ) { if ( currentlevel >= 20 ) diff --git a/src/playfab.cpp b/src/playfab.cpp index ab42bbc7b..a14dbe88e 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -550,10 +550,77 @@ void PlayfabUser_t::OnFunctionExecute(const PlayFab::CloudScriptModels::ExecuteF { if ( code == PlayFab::PlayFabErrorCode::PlayFabErrorSuccess ) { + std::set lidsImproved; + std::set lidsNew; + std::set lidsTotal; + int dungeonlvl = -1; + for ( auto itr = data.begin(); itr != data.end(); ++itr ) + { + std::string key = itr.name(); + { + if ( key == "dungeonlvl" ) + { + if ( data[key].isInt() ) + { + dungeonlvl = data[key].asInt(); + } + } + else if ( key == "improved_lids" ) + { + for ( int i = 0; i < data[key].size(); ++i ) + { + if ( data[key][i].isString() ) + { + lidsImproved.insert(data[key][i].asString()); + } + } + } + else if ( key == "new_lids" ) + { + for ( int i = 0; i < data[key].size(); ++i ) + { + if ( data[key][i].isString() ) + { + lidsNew.insert(data[key][i].asString()); + } + } + } + else if ( key == "lids" ) + { + for ( int i = 0; i < data[key].size(); ++i ) + { + if ( data[key][i].isString() ) + { + lidsTotal.insert(data[key][i].asString()); + } + } + } + } + } + if ( message.find("Score successfully published") != std::string::npos ) { // notification UIToastNotificationManager.createLeaderboardNotification(message); + if ( dungeonlvl >= 5 ) + { + for ( auto& lid : lidsNew ) + { + if ( lid.find("_seed_") != std::string::npos ) + { + steamStatisticUpdate(STEAM_STAT_DUNGEONSEED, STEAM_STAT_INT, 1); + break; + } + } + for ( auto& lid : lidsImproved ) + { + if ( lid.find("_seed_") != std::string::npos ) + { + steamAchievement("BARONY_ACH_GROWTH_MINDSET"); + break; + } + } + } } } playfabUser.postScoreHandler.queue.erase(it); diff --git a/src/scores.cpp b/src/scores.cpp index b43642ca6..3f6da0833 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -5018,6 +5018,60 @@ void AchievementObserver::checkMapScriptsOnVariableSet() } } +std::map dapperItems; +std::set AchievementObserver::PlayerAchievements::startingClassItems = +{ + LEATHER_HELM, + HAT_PHRYGIAN, + HAT_HOOD_WHISPERS, + HAT_WIZARD, + HAT_HOOD_APPRENTICE, + HAT_JESTER, + HAT_FEZ, + HAT_HOOD_ASSASSIN, + MONOCLE, + MASK_BANDIT, + PUNISHER_HOOD, + MASK_SHAMAN, + TOOL_BLINDFOLD_TELEPATHY, + TOOL_BLINDFOLD +}; + +int AchievementObserver::PlayerAchievements::getItemIndexForDapperAchievement(Item* item) +{ + if ( dapperItems.empty() ) + { + int index = 0; + for ( int i = 0; i < NUMITEMS; ++i ) + { + if ( startingClassItems.find((ItemType)i) != startingClassItems.end() ) + { + continue; + } + if ( items[i].item_slot == EQUIPPABLE_IN_SLOT_HELM ) + { + dapperItems[(ItemType)i] = index; + ++index; + } + else if ( items[i].item_slot == EQUIPPABLE_IN_SLOT_MASK ) + { + dapperItems[(ItemType)i] = index; + ++index; + } + } + } + + if ( item ) + { + auto find = dapperItems.find(item->type); + if ( find != dapperItems.end() ) + { + return find->second; + } + } + return -1; +} + void AchievementObserver::updatePlayerAchievement(int player, Achievement achievement, AchievementEvent achEvent) { switch ( achievement ) @@ -5119,6 +5173,61 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev } } break; + case BARONY_ACH_DAPPER: + { + + int loops = 2; + SteamStatIndexes statLevelTotal = STEAM_STAT_DAPPER; + while ( loops > 0 ) + { + --loops; + int index = playerAchievements[player].getItemIndexForDapperAchievement(loops == 1 ? stats[player]->helmet : stats[player]->mask); + if ( index >= 0 ) + { + SteamStatIndexes statBitCounter = STEAM_STAT_DAPPER_1; + if ( index >= 32 ) + { + statBitCounter = STEAM_STAT_DAPPER_2; + } + else if ( index >= 64 ) + { + statBitCounter = STEAM_STAT_DAPPER_3; + } + + Uint32 bit = (1 << (index % 32)); + if ( !(g_SteamStats[statBitCounter].m_iValue & (bit)) ) // bit not set + { + // update with the difference in values. + steamStatisticUpdate(statBitCounter, STEAM_STAT_INT, (bit)); + } + } + } + + if ( ticks % (TICKS_PER_SECOND * 5) == 0 ) + { + int hatmasksWorn = 0; + for ( int i = 0; i < 32; ++i ) + { + if ( g_SteamStats[STEAM_STAT_DAPPER_1].m_iValue & (1 << i) ) // count the bits + { + ++hatmasksWorn; + } + if ( g_SteamStats[STEAM_STAT_DAPPER_2].m_iValue & (1 << i) ) // count the bits + { + ++hatmasksWorn; + } + if ( g_SteamStats[STEAM_STAT_DAPPER_3].m_iValue & (1 << i) ) // count the bits + { + ++hatmasksWorn; + } + } + if ( hatmasksWorn >= g_SteamStats[statLevelTotal].m_iValue ) + { + steamStatisticUpdate(statLevelTotal, STEAM_STAT_INT, hatmasksWorn - g_SteamStats[statLevelTotal].m_iValue); + } + } + break; + } case BARONY_ACH_REAL_BOY: if ( achEvent == REAL_BOY_HUMAN_RECRUIT ) { @@ -5159,6 +5268,8 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev { std::unordered_set races; std::unordered_set classes; + std::vector awardAchievementsToAllPlayers; + int num = 0; for ( int i = 0; i < MAXPLAYERS; ++i ) { if ( !client_disconnected[i] ) @@ -5171,9 +5282,13 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev { classes.insert(client_classes[i]); } + ++num; } } - std::vector awardAchievementsToAllPlayers; + if ( gameModeManager.currentSession.challengeRun.isActive() && num >= 2 ) + { + awardAchievementsToAllPlayers.push_back(BARONY_ACH_SPROUTS); + } if ( !races.empty() ) { if ( races.find(RACE_INCUBUS) != races.end() && races.find(RACE_SUCCUBUS) != races.end() ) @@ -5342,6 +5457,9 @@ void AchievementObserver::awardAchievement(int player, int achievement) case BARONY_ACH_IRONIC_PUNISHMENT: steamAchievementClient(player, "BARONY_ACH_IRONIC_PUNISHMENT"); break; + case BARONY_ACH_SPROUTS: + steamAchievementClient(player, "BARONY_ACH_SPROUTS"); + break; default: messagePlayer(player, MESSAGE_DEBUG, "[WARNING]: Unhandled achievement: %d", achievement); break; diff --git a/src/scores.hpp b/src/scores.hpp index 68ec8cc1e..f8e845e37 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -106,7 +106,13 @@ enum SteamStatIndexes : int STEAM_STAT_EXTRA_CREDIT_LVLS, STEAM_STAT_DIPLOMA, STEAM_STAT_DIPLOMA_LVLS, - STEAM_STAT_TUTORIAL_ENTERED + STEAM_STAT_TUTORIAL_ENTERED, + STEAM_STAT_I_NEEDED_THAT, + STEAM_STAT_DAPPER_1, + STEAM_STAT_DAPPER_2, + STEAM_STAT_DAPPER_3, + STEAM_STAT_DAPPER, + STEAM_STAT_DUNGEONSEED }; enum SteamGlobalStatIndexes : int @@ -182,7 +188,7 @@ enum SteamGlobalStatIndexes : int SteamGlobalStatIndexes getIndexForDeathType(int type); -static const std::pair steamStatAchStringsAndMaxVals[] = +static const std::pair steamStatAchStringsAndMaxVals[] = { std::make_pair("BARONY_ACH_NONE", 999999), // STEAM_STAT_BOULDER_DEATHS, std::make_pair("BARONY_ACH_RHINESTONE_COWBOY", 50), // STEAM_STAT_RHINESTONE_COWBOY, @@ -232,7 +238,13 @@ static const std::pair steamStatAchStringsAndMaxVals[] = std::make_pair("BARONY_ACH_NONE", 1023), // STEAM_STAT_EXTRA_CREDIT_LVLS, std::make_pair("BARONY_ACH_DIPLOMA", 10), // STEAM_STAT_DIPLOMA std::make_pair("BARONY_ACH_NONE", 1023), // STEAM_STAT_DIPLOMA_LVLS, - std::make_pair("BARONY_ACH_NONE", 1) // STEAM_STAT_TUTORIAL_ENTERED, + std::make_pair("BARONY_ACH_NONE", 1), // STEAM_STAT_TUTORIAL_ENTERED, + std::make_pair("BARONY_ACH_I_NEEDED_THAT", 10), // STEAM_STAT_I_NEEDED_THAT + std::make_pair("BARONY_ACH_NONE", 0xFFFFFFFF), // STEAM_STAT_DAPPER_1 + std::make_pair("BARONY_ACH_NONE", 0xFFFFFFFF), // STEAM_STAT_DAPPER_2 + std::make_pair("BARONY_ACH_NONE", 0xFFFFFFFF), // STEAM_STAT_DAPPER_3 + std::make_pair("BARONY_ACH_DAPPER", 30), // STEAM_STAT_DAPPER + std::make_pair("BARONY_ACH_DUNGEONSEED", 12) // STEAM_STAT_DUNGEONSEED }; typedef struct score_t @@ -699,7 +711,9 @@ class AchievementObserver BARONY_ACH_DIPLOMA, BARONY_ACH_BACK_TO_BASICS, BARONY_ACH_FAST_LEARNER, - BARONY_ACH_MASTER + BARONY_ACH_MASTER, + BARONY_ACH_DAPPER, + BARONY_ACH_SPROUTS }; enum AchievementEvent : int { @@ -712,7 +726,8 @@ class AchievementObserver EXTRA_CREDIT_SECRET, DIPLOMA_LEVEL_COMPLETE, BACK_TO_BASICS_LEVEL_COMPLETE, - FAST_LEARNER_TIME_UPDATE + FAST_LEARNER_TIME_UPDATE, + DAPPER_EQUIPMENT_CHECK }; void updatePlayerAchievement(int player, Achievement achievement, AchievementEvent achEvent); bool bIsAchievementAllowedDuringTutorial(std::string achievementStr) @@ -798,6 +813,7 @@ class AchievementObserver std::unordered_set bountyTargets; bool updatedBountyTargets = false; bool wearingBountyHat = false; + static std::set startingClassItems; PlayerAchievements() { @@ -807,6 +823,7 @@ class AchievementObserver }; bool checkPathBetweenObjects(Entity* player, Entity* target, int achievement); bool checkTraditionKill(Entity* player, Entity* target); + int getItemIndexForDapperAchievement(Item* item); } playerAchievements[MAXPLAYERS]; void updateClientBounties(bool firstSend); diff --git a/src/steam.cpp b/src/steam.cpp index 15fc40ead..8c569aeae 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -411,6 +411,7 @@ std::string SteamServerClientWrapper::requestAuthTicket() authTicket = ""; char hexBuf[32]; + memset(hexBuf, 0, sizeof(hexBuf)); for ( int i = 0; i < cubTicket; ++i ) { snprintf(hexBuf, sizeof(hexBuf), "%02hhx", buf[i]); @@ -1009,6 +1010,8 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) case STEAM_STAT_GUERILLA_RADIO: case STEAM_STAT_ITS_A_LIVING: case STEAM_STAT_FASCIST: + case STEAM_STAT_I_NEEDED_THAT: + case STEAM_STAT_DUNGEONSEED: g_SteamStats[statisticNum].m_iValue = std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); break; @@ -1127,6 +1130,29 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); indicateProgress = false; break; + case STEAM_STAT_DAPPER_1: + case STEAM_STAT_DAPPER_2: + case STEAM_STAT_DAPPER_3: + g_SteamStats[statisticNum].m_iValue = + std::min((Uint32)g_SteamStats[statisticNum].m_iValue, (Uint32)steamStatAchStringsAndMaxVals[statisticNum].second); + indicateProgress = false; + break; + case STEAM_STAT_DAPPER: + g_SteamStats[statisticNum].m_iValue = + std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); + if ( g_SteamStats[statisticNum].m_iValue == steamStatAchStringsAndMaxVals[statisticNum].second ) + { + indicateProgress = true; + } + else if ( oldValue == g_SteamStats[statisticNum].m_iValue ) + { + indicateProgress = false; + } + else + { + indicateProgress = true; + } + break; case STEAM_STAT_DIPLOMA: case STEAM_STAT_EXTRA_CREDIT: g_SteamStats[statisticNum].m_iValue = @@ -1372,6 +1398,7 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) // below are 10 max value case STEAM_STAT_TAKE_THIS_OUTSIDE: case STEAM_STAT_SPICY: + case STEAM_STAT_I_NEEDED_THAT: if ( !achievementUnlocked(steamStatAchStringsAndMaxVals[statisticNum].first.c_str()) ) { if ( iVal == 1 || (iVal > 0 && iVal % 2 == 0) ) @@ -1383,12 +1410,23 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) break; case STEAM_STAT_DIPLOMA: case STEAM_STAT_EXTRA_CREDIT: + case STEAM_STAT_DUNGEONSEED: if ( !achievementUnlocked(steamStatAchStringsAndMaxVals[statisticNum].first.c_str()) ) { indicateAchievementProgressAndUnlock(steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), iVal, steamStatAchStringsAndMaxVals[statisticNum].second); } break; + case STEAM_STAT_DAPPER: + if ( !achievementUnlocked(steamStatAchStringsAndMaxVals[statisticNum].first.c_str()) ) + { + if ( iVal >= 5 && iVal % 5 == 0 ) + { + indicateAchievementProgressAndUnlock(steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), + iVal, steamStatAchStringsAndMaxVals[statisticNum].second); + } + } + break; default: break; }