Files
ReSkate/Extension/Progression/local_progression_runtime.cpp
T
2026-10-02 14:40:41 -05:00

100 lines
5.5 KiB
C++

#include "Extension/Profile/runtime_internal.h"
#include "local_challenge_runtime.h"
#include "local_neighborhood_runtime.h"
#include "local_rip_score_runtime.h"
namespace dingosdk {
using namespace profile_runtime;
// Public progression model and commands. Persistence owns all saved edits;
// existing native adapters hydrate them on their normal game-update paths.
ProgressionModel local_profile_progression() {
auto& s = local_runtime();
if (!s.active.load(std::memory_order_acquire)) return {};
std::lock_guard lock(s.native_mutex);
if (s.progression_revision != s.store->revision()) {
const auto saved_shared = s.store->shared_snapshot();
const auto& saved = *saved_shared;
ProgressionModel result;
result.available = true;
const auto owns = [&](std::string_view id) {
const auto it = saved.entitlements.find(id);
return it != saved.entitlements.end() && it->second;
};
for (const auto& stop : fixed_bus_stop_catalog())
result.bus_stops.push_back({stop.number, stop.world,
owns(stop.visibility_entitlement), owns(stop.collected_entitlement)});
const auto policy = profile::challenge_policy(saved);
result.challenges_enabled = policy.enabled;
result.challenges_hidden = s.store->bool_option(profile::hide_challenges_option).value_or(false);
for (const auto& [id, entry] : policy.catalog) {
ChallengeProgressRow row{id, entry.type, entry.available, 0,
profile::challenge_completed_criteria(saved, id), entry.goals.size(), entry.neighborhood};
const auto& section = saved.extensions.at("challenges");
if (section.contains("progress") && section.at("progress").contains(id))
row.attempts = section.at("progress").at(id).at("attempt").get<std::uint64_t>();
result.challenges.push_back(std::move(row));
}
const auto max = saved.bool_options.find(profile::max_neighborhood_ranks_option);
result.ranks_maxed = max != saved.bool_options.end() && max->second;
constexpr std::array<const char*, 4> names{"Entertainment", "Financial", "Historic", "Stadium"};
for (unsigned i = 0; i < profile::neighborhood_ids.size(); ++i) {
const auto id = profile::neighborhood_ids[i];
const auto rank = saved.neighborhood_ranks.find(id);
result.districts[i] = {std::string(id), names[i], rank == saved.neighborhood_ranks.end() ? 0 : rank->second};
}
if (const auto score = profile::rip_score(saved)) {
result.score_available = true;
result.score = score->value; result.score_cap = score->cap; result.score_level = score->level;
}
s.progression_cache = std::move(result);
s.progression_revision = saved.revision;
}
auto result = s.progression_cache;
result.feedback = s.progression_feedback;
return result;
}
bool set_local_progression(const std::vector<std::string>& args) {
auto& s = local_runtime();
if (!s.active.load(std::memory_order_acquire)) return false;
std::lock_guard lock(s.native_mutex);
try {
const auto number = [&](unsigned index, std::uint64_t maximum) {
if (index >= args.size()) throw std::runtime_error("Missing progression value.");
const auto& text = args[index]; std::uint64_t value{};
const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value);
if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value > maximum)
throw std::runtime_error("Invalid progression value.");
return value;
};
if (args.size() == 3 && args[0] == "busstop") {
s.store->set_bus_stop_state(static_cast<unsigned>(number(1, UINT8_MAX)), static_cast<unsigned>(number(2, 2)));
s.progression_feedback = "Bus stop saved. Reload the level to refresh its icon and interaction.";
} else if (args.size() == 2 && args[0] == "challenges") {
const bool hidden = number(1, 1) == 0;
s.store->set_bool_option(profile::hide_challenges_option, hidden);
hidden_challenges().store(hidden, std::memory_order_release);
challenge_runtime().next_catalog_poll = 0;
s.progression_feedback = hidden ? "Challenges hidden. Saved progress is kept." : "Challenges shown.";
} else if (args.size() == 4 && args[0] == "ripscore") {
s.store->save_rip_score({static_cast<std::int64_t>(number(1, 1000000000)),
static_cast<std::int64_t>(number(2, 1000000000)), static_cast<std::int32_t>(number(3, 10000))});
rip_score_runtime().next_poll = 0;
s.progression_feedback = "RIP score saved.";
} else if (args.size() == 3 && args[0] == "district") {
s.store->save_district_rank(args[1], static_cast<std::uint32_t>(number(2, 10000)));
neighborhood_runtime().next_poll = 0;
s.progression_feedback = "District level saved. Automatic max ranks is off.";
} else if (args.size() == 2 && args[0] == "maxranks") {
s.store->set_bool_option(profile::max_neighborhood_ranks_option, number(1, 1) != 0);
neighborhood_runtime().next_poll = 0;
s.progression_feedback = "District rank preference saved.";
} else throw std::runtime_error("Unknown progression command.");
return true;
} catch (const std::exception& error) {
s.progression_feedback = std::string("Not saved: ") + error.what();
return false;
}
}
}