Skip to content

Commit

Permalink
add attempt statistic collection
Browse files Browse the repository at this point in the history
  • Loading branch information
Cvolton committed Oct 5, 2023
1 parent 1dff5b9 commit 0c396b5
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 4 deletions.
23 changes: 23 additions & 0 deletions src/hooks/PlayLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@

#include "../managers/BetterInfoScheduler.h"
#include "../managers/BetterInfoStats.h"
#include "../managers/BetterInfoStatsV2.h"
#include "../managers/BetterInfoCache.h"

using namespace geode::prelude;

class $modify(BIPlayLayer, PlayLayer) {

inline static bool needsReset = false;

void levelComplete(){
PlayLayer::levelComplete();

Expand Down Expand Up @@ -36,7 +39,27 @@ class $modify(BIPlayLayer, PlayLayer) {
PlayLayer::onQuit();
}

void destroyPlayer(PlayerObject* player, GameObject* spike) {
PlayLayer::destroyPlayer(player, spike);

if(!m_isDead || needsReset) return;
needsReset = true;

log::info("death x: {}", player->m_position.x);
log::info("death y: {}", player->m_position.y);
log::info("death rotation: {}", player->getRotation());
log::info("percent: {}", m_lastDeathPercent);
BetterInfoStatsV2::sharedState()->logDeath(m_level, m_isPracticeMode, LevelDeath {
.percentage = m_lastDeathPercent,
.x = player->m_position.x,
.y = player->m_position.y,
.rotation = player->getRotation()
});
}

void resetLevel(){
needsReset = false;

PlayLayer::resetLevel();

auto stats = BetterInfoStats::sharedState();
Expand Down
4 changes: 4 additions & 0 deletions src/layers/ExtendedLevelInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "PaginatedFLAlert.h"
#include "../utils.hpp"
#include "../managers/BetterInfoStats.h"
#include "../managers/BetterInfoStatsV2.h"
#include "../managers/BetterInfoCache.h"

#include <deque>
Expand Down Expand Up @@ -416,6 +417,7 @@ void ExtendedLevelInfo::showProgressDialog(GJGameLevel* level){

if(level->m_levelType != GJLevelType::Editor){
auto stats = BetterInfoStats::sharedState();
auto statsV2 = BetterInfoStatsV2::sharedState();

contentStream << "\n<cp>Normal</c>: " << level->m_normalPercent
<< "%\n<co>Practice</c>: " << level->m_practicePercent << "%";
Expand All @@ -424,6 +426,7 @@ void ExtendedLevelInfo::showProgressDialog(GJGameLevel* level){

auto normalAttempts = stats->getAttempts(level, false);
auto practiceAttempts = stats->getAttempts(level, true);
auto commonFail = statsV2->getCommonFail(level);
secondStream << "<cp>Attempts (normal)</c>: " << normalAttempts << "\n";
secondStream << "<co>Attempts (practice)</c>: " << practiceAttempts << "\n";
if((normalAttempts + practiceAttempts) != level->m_attempts) secondStream << "<cy>Attempts (unknown)</c>: " << (level->m_attempts - practiceAttempts - normalAttempts) << "\n";
Expand All @@ -432,6 +435,7 @@ void ExtendedLevelInfo::showProgressDialog(GJGameLevel* level){
if(stats->getPlay(level, true) != 0) secondStream << "<cl>Last played</c>: " << BetterInfo::timeToString(stats->getPlay(level, true)) << "\n";
if(stats->getCompletion(level, false) > 0) secondStream << "<cp>Completed</c>: " << BetterInfo::timeToString(stats->getCompletion(level, false)) << "\n";
if(stats->getCompletion(level, true) > 0) secondStream << "<co>Completed (practice)</c>: " << BetterInfo::timeToString(stats->getCompletion(level, true)) << "\n";
secondStream << "<co>Common fail:</c> " << commonFail.first << "% (" << commonFail.second << "x)\n";
}else{
contentStream << "\n<cp>Objects</c>: " << level->m_objectCount
<< "\n<cr>In Editor</c>: " << ExtendedLevelInfo::workingTime(level->m_workingTime) << "\n";
Expand Down
14 changes: 14 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include <Geode/Geode.hpp>

#include "managers/BetterInfoStatsV2.h"
#include "managers/BetterInfoCache.h"

using namespace geode::prelude;

void setupPageLimitBypassWindows() {
Expand Down Expand Up @@ -46,6 +49,17 @@ void setupPageLimitBypass() {

}

void loadManagers() {
std::thread([] {
BetterInfoStatsV2::sharedState();
BetterInfoCache::sharedState();
}).detach();
}

$execute {
setupPageLimitBypass();
}

$on_mod(Loaded) {
loadManagers();
}
15 changes: 11 additions & 4 deletions src/managers/BaseJsonManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Result<> BaseJsonManager::load() {
}

validateLoadedData();
m_loaded = true;

return Ok();
}
Expand All @@ -49,10 +50,16 @@ Result<> BaseJsonManager::save() {
}

void BaseJsonManager::doSave() {
auto loadResult = save();
if(!loadResult) {
log::warn("Unable to load {}", m_filename);
}
std::thread([this] {
auto loadResult = save();
if(!loadResult) {
log::warn("Unable to save {}", m_filename);
}
}).detach();
}

void BaseJsonManager::waitForLoad() {
while(!m_loaded) {}
}

void BaseJsonManager::validateLoadedData() {
Expand Down
3 changes: 3 additions & 0 deletions src/managers/BaseJsonManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using namespace geode::prelude;

class BaseJsonManager : public CCObject {
std::atomic_bool m_loaded = false;

protected:
BaseJsonManager();

Expand All @@ -16,6 +18,7 @@ class BaseJsonManager : public CCObject {
Result<> load();
Result<> save();
void doSave();
void waitForLoad();

virtual void validateLoadedData();
void validateIsObject(const char* key);
Expand Down
64 changes: 64 additions & 0 deletions src/managers/BetterInfoStatsV2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "BetterInfoStatsV2.h"
#include "../utils.hpp"

#include <fstream>
#include <Geode/utils/web.hpp>

bool BetterInfoStatsV2::init(){
if(!BaseJsonManager::init("stats.json")) return false;
return true;
}

void BetterInfoStatsV2::validateLoadedData() {
validateIsObject("levels");
validateIsObject("levels-daily");
validateIsObject("levels-gauntlet");
}

BetterInfoStatsV2::BetterInfoStatsV2(){}

auto& BetterInfoStatsV2::levelObject(GJGameLevel* gjLevel) {
waitForLoad();

const char* key = "levels";
if(gjLevel->m_dailyID > 0) key = "levels-daily";
if(gjLevel->m_gauntletLevel) key = "levels-gauntlet";

auto idStr = std::to_string(gjLevel->m_levelID.value());
auto& levels = m_json[key];
if(!levels[idStr].is_object()) levels[idStr] = json::Object();
auto& level = levels[idStr];
if(!level["attempts"].is_array()) level["attempts"] = json::Array();
return level;
}

void BetterInfoStatsV2::logDeath(GJGameLevel* level, bool practice, LevelDeath death) {
if(practice) return;
if(level->m_levelType == GJLevelType::Editor) return;

levelObject(level)["attempts"].as_array().push_back(death);
doSave();
}

std::pair<int, int> BetterInfoStatsV2::getCommonFail(GJGameLevel* gjLevel) {
std::unordered_map<int, int> fails;

auto attempts = levelObject(gjLevel)["attempts"].as_array();
if(attempts.size() == 0) return {0,0};

for(auto& attempt : attempts) {
if(!attempt.is_object()) continue;
if(!attempt["percentage"].is_number()) continue;

fails[attempt["percentage"].as_int()] += 1;
}

auto max = std::max_element(
fails.begin(), fails.end(),
[] (const std::pair<int, int>& a, const std::pair<int, int>& b) {
return a.second < b.second;
}
);

return *max;
}
27 changes: 27 additions & 0 deletions src/managers/BetterInfoStatsV2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include "BaseJsonManager.h"
#include "../objects/LevelDeath.h"

class BetterInfoStatsV2 : public BaseJsonManager {
inline static BetterInfoStatsV2* m_instance = nullptr;
BetterInfoStatsV2();

public:
bool init();

void validateLoadedData();

auto& levelObject(GJGameLevel* level);
void logDeath(GJGameLevel* level, bool practice, LevelDeath death);
std::pair<int, int> getCommonFail(GJGameLevel* gjLevel);

static BetterInfoStatsV2* sharedState(){
if(m_instance == nullptr){
m_instance = new BetterInfoStatsV2;
m_instance->init();
m_instance->retain();
}
return m_instance;
}
};
32 changes: 32 additions & 0 deletions src/objects/LevelDeath.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <time.h>

struct LevelDeath {
public:
int percentage;
float x, y, rotation;
time_t time = std::time(nullptr);
};

template <>
struct json::Serialize<LevelDeath> {
static LevelDeath from_json(const json::Value& value) {
return LevelDeath {
.percentage = value["percentage"].as_int(),
.x = (float) value["x"].as_double(),
.y = (float) value["y"].as_double(),
.rotation = (float) value["rotation"].as_double(),
.time = value["time"].as_int()
};
}
static json::Value to_json(const LevelDeath& death) {
return json::Object {
{ "percentage", death.percentage },
{ "x", death.x },
{ "y", death.y },
{ "rotation", death.rotation },
{ "time", death.time }
};
}
};

0 comments on commit 0c396b5

Please sign in to comment.