mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-05-30 00:56:18 -04:00
bcf9f392f0
* Adds in-game display of certain rando collectibles. Before, these were only available through the Item Tracker ImGui Window. With this commit, they can be accessed via holding C-Up on the Map Screen. Currently I've added Greg and Triforce Pieces (when applicable) to this menu. Boss Souls, Ocarina Buttons, and eventually Silver Rupees will be added later. * Adds ocarina buttons to in-game display * Initial pass on rendering the text on a black background. * Starting to render boss soul icons * Better alignment and rendering of Boss Soul icons. * Adds icons prefixing the list entries. * Switches boss souls to 32x32 icon. * Partially working Matrix/Vtx implementation Currently hardcoded Greg text, replacing map screen completely. * Now rendering properly thanks to Archez! * Better implementation of accessing the new page. - now attached to Quest status instead of Map - now triggered by a toggle instead of holding a button - now has its own background (temporarily save screen but will be replaced with something custom later) * Make KaleidoEntry's reactive to game state Adds Greg proper and Triforce Hunt to the Misc. Collectibles Page. * Conditionally render Triforce Hunt * Documentation/Cleanup * WIP Ocarina Buttons rendering * Working ocarina buttons display * Renders buttons as Gray instead of using Grayscale This may seem inconsistent, but with Grayscale they technically render as different shades of gray, especially with custom cosmetics. With this they now render as the same shade of gray. * Makes Ocarina Icon gray when no buttons have been collected. * Adds Boss Souls. Currently they run off the menu, need to implement scrolling. * Implement Scrolling for the menu. Need to figure out how to throttle the stick inputs still. * Moves input handling to draw function. I hate it but that's how Kaleido does it and there's some input throttling logic in there, so in order to make this feel like a kaleido menu I have to also handle input in the draw function. * Removes custom cosmetic handling of Ocarina Buttons. I've chosen not to respect the cosmetics for the sake of accessibility and color contrast, but the code is still present and commented out in case we want to reverse that decision. * Hopefully fixes mac build errors. * Implements update function via Hook. * Another mac fix hopefully * Cleans up unused code from the rectangle based attempt. * Clean up more unused code * Commit Boss Soul icon * Fix typo * Remove commented code * Improve toggle functionality * Re-introduce cosmetic matching for ocarina buttons * Revert some unnecessary formatting changes * Fix cursor/page turning issue More improvements to come here (drawing arrows, custom text at the bottom, etc.) * Fix some more formatting changes * One last batch of formatting reverts
442 lines
15 KiB
C++
442 lines
15 KiB
C++
#include "context.h"
|
|
#include "static_data.h"
|
|
#include "soh/OTRGlobals.h"
|
|
#include "soh/Enhancements/item-tables/ItemTableManager.h"
|
|
#include "3drando/shops.hpp"
|
|
#include "dungeon.h"
|
|
#include "logic.h"
|
|
#include "entrance.h"
|
|
#include "settings.h"
|
|
#include "rando_hash.h"
|
|
#include "fishsanity.h"
|
|
#include "macros.h"
|
|
#include "3drando/hints.hpp"
|
|
#include "../kaleido.h"
|
|
|
|
#include <fstream>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
namespace Rando {
|
|
std::weak_ptr<Context> Context::mContext;
|
|
|
|
Context::Context() {
|
|
|
|
for (int i = 0; i < RC_MAX; i++) {
|
|
itemLocationTable[i] = ItemLocation(static_cast<RandomizerCheck>(i));
|
|
}
|
|
mEntranceShuffler = std::make_shared<EntranceShuffler>();
|
|
mDungeons = std::make_shared<Dungeons>();
|
|
mLogic = std::make_shared<Logic>();
|
|
mTrials = std::make_shared<Trials>();
|
|
mSettings = std::make_shared<Settings>();
|
|
mFishsanity = std::make_shared<Fishsanity>();
|
|
}
|
|
|
|
RandomizerArea Context::GetAreaFromString(std::string str) {
|
|
return (RandomizerArea)StaticData::areaNameToEnum[str];
|
|
}
|
|
|
|
void Context::InitStaticData() {
|
|
StaticData::HintTable_Init();
|
|
StaticData::trialNameToEnum = StaticData::PopulateTranslationMap(StaticData::trialData);
|
|
StaticData::hintNameToEnum = StaticData::PopulateTranslationMap(StaticData::hintNames);
|
|
StaticData::hintTypeNameToEnum = StaticData::PopulateTranslationMap(StaticData::hintTypeNames);
|
|
StaticData::areaNameToEnum = StaticData::PopulateTranslationMap(StaticData::areaNames);
|
|
StaticData::InitLocationTable();
|
|
}
|
|
|
|
std::shared_ptr<Context> Context::CreateInstance() {
|
|
if (mContext.expired()) {
|
|
auto instance = std::make_shared<Context>();
|
|
mContext = instance;
|
|
GetInstance()->GetLogic()->SetContext(GetInstance());
|
|
return instance;
|
|
}
|
|
return GetInstance();
|
|
}
|
|
|
|
std::shared_ptr<Context> Context::GetInstance() {
|
|
return mContext.lock();
|
|
}
|
|
|
|
Hint* Context::GetHint(const RandomizerHint hintKey) {
|
|
return &hintTable[hintKey];
|
|
}
|
|
|
|
void Context::AddHint(const RandomizerHint hintId, const Hint hint) {
|
|
hintTable[hintId] = hint; //RANDOTODO this should probably be an rvalue
|
|
}
|
|
|
|
ItemLocation* Context::GetItemLocation(const RandomizerCheck locKey) {
|
|
return &itemLocationTable[locKey];
|
|
}
|
|
|
|
ItemLocation* Context::GetItemLocation(size_t locKey) {
|
|
return &itemLocationTable[static_cast<RandomizerCheck>(locKey)];
|
|
}
|
|
|
|
ItemOverride& Context::GetItemOverride(RandomizerCheck locKey) {
|
|
if (!overrides.contains(locKey)) {
|
|
overrides.emplace(locKey, ItemOverride());
|
|
}
|
|
return overrides.at(locKey);
|
|
}
|
|
|
|
ItemOverride& Context::GetItemOverride(size_t locKey) {
|
|
if (!overrides.contains(static_cast<RandomizerCheck>(locKey))) {
|
|
overrides.emplace(static_cast<RandomizerCheck>(locKey), ItemOverride());
|
|
}
|
|
return overrides.at(static_cast<RandomizerCheck>(locKey));
|
|
}
|
|
|
|
void Context::PlaceItemInLocation(const RandomizerCheck locKey, const RandomizerGet item, const bool applyEffectImmediately,
|
|
const bool setHidden) {
|
|
const auto loc = GetItemLocation(locKey);
|
|
SPDLOG_DEBUG("\n");
|
|
SPDLOG_DEBUG(StaticData::RetrieveItem(item).GetName().GetEnglish());
|
|
SPDLOG_DEBUG(" placed at ");
|
|
SPDLOG_DEBUG(StaticData::GetLocation(locKey)->GetName());
|
|
SPDLOG_DEBUG("\n\n");
|
|
|
|
if (applyEffectImmediately || mSettings->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_GLITCHLESS) || mSettings->GetOption(RSK_LOGIC_RULES).Is(RO_LOGIC_VANILLA)) {
|
|
StaticData::RetrieveItem(item).ApplyEffect();
|
|
}
|
|
|
|
// TODO? Show Progress
|
|
|
|
// If we're placing a non-shop item in a shop location, we want to record it for custom messages
|
|
if (StaticData::RetrieveItem(item).GetItemType() != ITEMTYPE_SHOP &&
|
|
StaticData::GetLocation(locKey)->GetRCType() == RCTYPE_SHOP) {
|
|
NonShopItems[locKey].Name = StaticData::RetrieveItem(item).GetName();
|
|
NonShopItems[locKey].Repurchaseable =
|
|
StaticData::RetrieveItem(item).GetItemType() == ITEMTYPE_REFILL ||
|
|
StaticData::RetrieveItem(item).GetHintKey() == RHT_PROGRESSIVE_BOMBCHUS;
|
|
}
|
|
|
|
loc->SetPlacedItem(item);
|
|
if (setHidden) {
|
|
loc->SetHidden(true);
|
|
}
|
|
}
|
|
|
|
void Context::AddLocation(const RandomizerCheck loc, std::vector<RandomizerCheck>* destination) {
|
|
if (destination == nullptr) {
|
|
destination = &allLocations;
|
|
}
|
|
destination->push_back(loc);
|
|
}
|
|
|
|
template <typename Container>
|
|
void Context::AddLocations(const Container& locations, std::vector<RandomizerCheck>* destination) {
|
|
if (destination == nullptr) {
|
|
destination = &allLocations;
|
|
}
|
|
destination->insert(destination->end(), std::cbegin(locations), std::cend(locations));
|
|
}
|
|
|
|
void Context::GenerateLocationPool() {
|
|
allLocations.clear();
|
|
//AddLocation(RC_LINKS_POCKET); this is being added twice now
|
|
if (mSettings->GetOption(RSK_TRIFORCE_HUNT)) {
|
|
AddLocation(RC_TRIFORCE_COMPLETED);
|
|
}
|
|
AddLocations(StaticData::GetOverworldLocations());
|
|
|
|
if (mSettings->GetOption(RSK_FISHSANITY).IsNot(RO_FISHSANITY_OFF)) {
|
|
AddLocations(mFishsanity->GetFishsanityLocations().first);
|
|
}
|
|
|
|
for (const auto dungeon : mDungeons->GetDungeonList()) {
|
|
AddLocations(dungeon->GetDungeonLocations());
|
|
}
|
|
}
|
|
|
|
void Context::AddExcludedOptions() {
|
|
AddLocations(StaticData::GetOverworldLocations(), &everyPossibleLocation);
|
|
for (const auto dungeon : mDungeons->GetDungeonList()) {
|
|
AddLocations(dungeon->GetEveryLocation(), &everyPossibleLocation);
|
|
}
|
|
for (const RandomizerCheck rc : everyPossibleLocation) {
|
|
GetItemLocation(rc)->AddExcludeOption();
|
|
}
|
|
}
|
|
|
|
std::vector<RandomizerCheck> Context::GetLocations(const std::vector<RandomizerCheck>& locationPool, const RandomizerCheckType checkType) {
|
|
std::vector<RandomizerCheck> locationsOfType;
|
|
for (RandomizerCheck locKey : locationPool) {
|
|
if (StaticData::GetLocation(locKey)->GetRCType() == checkType) {
|
|
locationsOfType.push_back(locKey);
|
|
}
|
|
}
|
|
return locationsOfType;
|
|
}
|
|
|
|
void Context::ClearItemLocations() {
|
|
for (size_t i = 0; i < itemLocationTable.size(); i++) {
|
|
GetItemLocation(static_cast<RandomizerCheck>(i))->ResetVariables();
|
|
}
|
|
}
|
|
|
|
void Context::ItemReset() {
|
|
for (const RandomizerCheck il : allLocations) {
|
|
GetItemLocation(il)->ResetVariables();
|
|
}
|
|
|
|
for (const RandomizerCheck il : StaticData::dungeonRewardLocations) {
|
|
GetItemLocation(il)->ResetVariables();
|
|
}
|
|
}
|
|
|
|
void Context::LocationReset() {
|
|
for (auto& il : itemLocationTable) {
|
|
il.RemoveFromPool();
|
|
}
|
|
}
|
|
|
|
void Context::HintReset() {
|
|
for (const RandomizerCheck il : StaticData::GetGossipStoneLocations()) {
|
|
GetItemLocation(il)->ResetVariables();
|
|
}
|
|
for (Hint& hint : hintTable){
|
|
hint.ResetVariables();
|
|
}
|
|
}
|
|
|
|
void Context::CreateItemOverrides() {
|
|
SPDLOG_DEBUG("NOW CREATING OVERRIDES\n\n");
|
|
for (RandomizerCheck locKey : allLocations) {
|
|
const auto loc = StaticData::GetLocation(locKey);
|
|
// If this is an ice trap, store the disguise model in iceTrapModels
|
|
const auto itemLoc = GetItemLocation(locKey);
|
|
if (itemLoc->GetPlacedRandomizerGet() == RG_ICE_TRAP) {
|
|
ItemOverride val(locKey, RandomElement(possibleIceTrapModels));
|
|
iceTrapModels[locKey] = val.LooksLike();
|
|
val.SetTrickName(GetIceTrapName(val.LooksLike()));
|
|
// If this is ice trap is in a shop, change the name based on what the model will look like
|
|
if (loc->GetRCType() == RCTYPE_SHOP) {
|
|
NonShopItems[locKey].Name = val.GetTrickName();
|
|
}
|
|
overrides[locKey] = val;
|
|
}
|
|
SPDLOG_DEBUG(loc->GetName());
|
|
SPDLOG_DEBUG(": ");
|
|
SPDLOG_DEBUG(itemLoc->GetPlacedItemName().GetEnglish());
|
|
SPDLOG_DEBUG("\n");
|
|
}
|
|
SPDLOG_DEBUG("Overrides Created: ");
|
|
SPDLOG_DEBUG(std::to_string(overrides.size()));
|
|
}
|
|
|
|
bool Context::IsSeedGenerated() const {
|
|
return mSeedGenerated;
|
|
}
|
|
|
|
void Context::SetSeedGenerated(const bool seedGenerated) {
|
|
mSeedGenerated = seedGenerated;
|
|
}
|
|
|
|
bool Context::IsSpoilerLoaded() const {
|
|
return mSpoilerLoaded;
|
|
}
|
|
|
|
void Context::SetSpoilerLoaded(const bool spoilerLoaded) {
|
|
mSpoilerLoaded = spoilerLoaded;
|
|
}
|
|
|
|
bool Context::IsPlandoLoaded() const {
|
|
return mPlandoLoaded;
|
|
}
|
|
|
|
void Context::SetPlandoLoaded(const bool plandoLoaded) {
|
|
mPlandoLoaded = plandoLoaded;
|
|
}
|
|
|
|
GetItemEntry Context::GetFinalGIEntry(const RandomizerCheck rc, const bool checkObtainability, const GetItemID ogItemId) {
|
|
const auto itemLoc = GetItemLocation(rc);
|
|
if (itemLoc->GetPlacedRandomizerGet() == RG_NONE) {
|
|
if (ogItemId != GI_NONE) {
|
|
return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, ogItemId);
|
|
}
|
|
return ItemTableManager::Instance->RetrieveItemEntry(
|
|
MOD_NONE, StaticData::RetrieveItem(StaticData::GetLocation(rc)->GetVanillaItem()).GetItemID());
|
|
}
|
|
if (checkObtainability && OTRGlobals::Instance->gRandomizer->GetItemObtainabilityFromRandomizerGet(
|
|
itemLoc->GetPlacedRandomizerGet()) != CAN_OBTAIN) {
|
|
return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, GI_RUPEE_BLUE);
|
|
}
|
|
GetItemEntry giEntry = itemLoc->GetPlacedItem().GetGIEntry_Copy();
|
|
if (overrides.contains(rc)) {
|
|
const auto fakeGiEntry = StaticData::RetrieveItem(overrides[rc].LooksLike()).GetGIEntry();
|
|
giEntry.gid = fakeGiEntry->gid;
|
|
giEntry.gi = fakeGiEntry->gi;
|
|
giEntry.drawItemId = fakeGiEntry->drawItemId;
|
|
giEntry.drawModIndex = fakeGiEntry->drawModIndex;
|
|
giEntry.drawFunc = fakeGiEntry->drawFunc;
|
|
}
|
|
return giEntry;
|
|
}
|
|
|
|
std::string sanitize(std::string stringValue) {
|
|
// Add backslashes.
|
|
for (auto i = stringValue.begin();;) {
|
|
auto const pos =
|
|
std::find_if(i, stringValue.end(), [](char const c) { return '\\' == c || '\'' == c || '"' == c; });
|
|
if (pos == stringValue.end()) {
|
|
break;
|
|
}
|
|
i = std::next(stringValue.insert(pos, '\\'), 2);
|
|
}
|
|
|
|
// Removes others.
|
|
std::erase_if(stringValue, [](char const c) { return '\n' == c || '\r' == c || '\0' == c || '\x1A' == c; });
|
|
|
|
return stringValue;
|
|
}
|
|
|
|
void Context::ParseSpoiler(const char* spoilerFileName, const bool plandoMode) {
|
|
std::ifstream spoilerFileStream(sanitize(spoilerFileName));
|
|
if (!spoilerFileStream) {
|
|
return;
|
|
}
|
|
mSeedGenerated = false;
|
|
mSpoilerLoaded = false;
|
|
mPlandoLoaded = false;
|
|
try {
|
|
nlohmann::json spoilerFileJson;
|
|
spoilerFileStream >> spoilerFileJson;
|
|
ParseHashIconIndexesJson(spoilerFileJson);
|
|
mSettings->ParseJson(spoilerFileJson);
|
|
if (plandoMode) {
|
|
ParseItemLocationsJson(spoilerFileJson);
|
|
ParseHintJson(spoilerFileJson);
|
|
mEntranceShuffler->ParseJson(spoilerFileJson);
|
|
mDungeons->ParseJson(spoilerFileJson);
|
|
mTrials->ParseJson(spoilerFileJson);
|
|
mPlandoLoaded = true;
|
|
}
|
|
mSpoilerLoaded = true;
|
|
mSeedGenerated = false;
|
|
} catch (...) {
|
|
LUSLOG_ERROR("Failed to load Spoiler File: %s", spoilerFileName);
|
|
}
|
|
}
|
|
|
|
void Context::ParseHashIconIndexesJson(nlohmann::json spoilerFileJson) {
|
|
nlohmann::json hashJson = spoilerFileJson["file_hash"];
|
|
int index = 0;
|
|
for (auto it = hashJson.begin(); it != hashJson.end(); ++it) {
|
|
hashIconIndexes[index] = gSeedTextures[it.value()].id;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) {
|
|
nlohmann::json locationsJson = spoilerFileJson["locations"];
|
|
for (auto it = locationsJson.begin(); it != locationsJson.end(); ++it) {
|
|
RandomizerCheck rc = StaticData::locationNameToEnum[it.key()];
|
|
if (it->is_structured()) {
|
|
nlohmann::json itemJson = *it;
|
|
for (auto itemit = itemJson.begin(); itemit != itemJson.end(); ++itemit) {
|
|
if (itemit.key() == "item") {
|
|
itemLocationTable[rc].SetPlacedItem(StaticData::itemNameToEnum[itemit.value().get<std::string>()]);
|
|
} else if (itemit.key() == "price") {
|
|
itemLocationTable[rc].SetCustomPrice(itemit.value().get<uint16_t>());
|
|
} else if (itemit.key() == "model") {
|
|
overrides[rc] = ItemOverride(rc, StaticData::itemNameToEnum[itemit.value().get<std::string>()]);
|
|
} else if (itemit.key() == "trickName") {
|
|
overrides[rc].SetTrickName(Text(itemit.value().get<std::string>()));
|
|
}
|
|
}
|
|
} else {
|
|
itemLocationTable[rc].SetPlacedItem(StaticData::itemNameToEnum[it.value().get<std::string>()]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Context::WriteHintJson(nlohmann::ordered_json& spoilerFileJson){
|
|
for (Hint hint: hintTable){
|
|
hint.logHint(spoilerFileJson);
|
|
}
|
|
}
|
|
|
|
nlohmann::json getValueForMessage(std::unordered_map<std::string, nlohmann::json> map, CustomMessage message){
|
|
std::vector<std::string> strings = message.GetAllMessages();
|
|
for (uint8_t language = 0; language < LANGUAGE_MAX; language++){
|
|
if (map.contains(strings[language])){
|
|
return strings[language];
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void Context::ParseHintJson(nlohmann::json spoilerFileJson) {
|
|
for (auto hintData : spoilerFileJson["Gossip Stone Hints"].items()){
|
|
RandomizerHint hint = (RandomizerHint)StaticData::hintNameToEnum[hintData.key()];
|
|
AddHint(hint, Hint(hint, hintData.value()));
|
|
}
|
|
for (auto hintData : spoilerFileJson["Static Hints"].items()){
|
|
RandomizerHint hint = (RandomizerHint)StaticData::hintNameToEnum[hintData.key()];
|
|
AddHint(hint, Hint(hint, hintData.value()));
|
|
}
|
|
CreateStaticHints();
|
|
}
|
|
|
|
std::shared_ptr<Settings> Context::GetSettings() {
|
|
return mSettings;
|
|
}
|
|
|
|
std::shared_ptr<EntranceShuffler> Context::GetEntranceShuffler() {
|
|
return mEntranceShuffler;
|
|
}
|
|
|
|
std::shared_ptr<Dungeons> Context::GetDungeons() {
|
|
return mDungeons;
|
|
}
|
|
|
|
std::shared_ptr<Fishsanity> Context::GetFishsanity() {
|
|
return mFishsanity;
|
|
}
|
|
|
|
DungeonInfo* Context::GetDungeon(size_t key) const {
|
|
return mDungeons->GetDungeon(static_cast<DungeonKey>(key));
|
|
}
|
|
|
|
std::shared_ptr<Logic> Context::GetLogic() {
|
|
if (mLogic.get() == nullptr) {
|
|
mLogic = std::make_shared<Logic>();
|
|
}
|
|
return mLogic;
|
|
}
|
|
|
|
std::shared_ptr<Trials> Context::GetTrials() {
|
|
return mTrials;
|
|
}
|
|
|
|
TrialInfo* Context::GetTrial(size_t key) const {
|
|
return mTrials->GetTrial(static_cast<TrialKey>(key));
|
|
}
|
|
|
|
TrialInfo* Context::GetTrial(TrialKey key) const {
|
|
return mTrials->GetTrial(key);
|
|
}
|
|
|
|
Sprite* Context::GetSeedTexture(const uint8_t index) {
|
|
return &gSeedTextures[index];
|
|
}
|
|
|
|
Option& Context::GetOption(const RandomizerSettingKey key) const {
|
|
return mSettings->GetOption(key);
|
|
}
|
|
|
|
TrickOption& Context::GetTrickOption(const RandomizerTrick key) const {
|
|
return mSettings->GetTrickOption(key);
|
|
}
|
|
|
|
std::shared_ptr<Kaleido> Context::GetKaleido() {
|
|
if (mKaleido == nullptr) {
|
|
mKaleido = std::make_shared<Kaleido>();
|
|
}
|
|
return mKaleido;
|
|
}
|
|
} // namespace Rando
|