mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-07-16 10:02:59 -07:00
Merge branch 'develop' into ItemName
This commit is contained in:
commit
f7c358dff8
25 changed files with 453 additions and 68 deletions
|
@ -10,6 +10,8 @@ set(CVAR_PREFIX_TRACKER "gTrackers")
|
|||
set(CVAR_PREFIX_DEVELOPER_TOOLS "gDeveloperTools")
|
||||
set(CVAR_PREFIX_GENERAL "gGeneral")
|
||||
set(CVAR_PREFIX_REMOTE "gRemote")
|
||||
set(CVAR_PREFIX_GAMEPLAY_STATS "gGameplayStats")
|
||||
set(CVAR_PREFIX_TIME_DISPLAY "gTimeDisplay")
|
||||
add_compile_definitions(
|
||||
CVAR_PREFIX_RANDOMIZER_ENHANCEMENT="${CVAR_PREFIX_RANDOMIZER_ENHANCEMENT}"
|
||||
CVAR_PREFIX_RANDOMIZER_SETTING="${CVAR_PREFIX_RANDOMIZER_SETTING}"
|
||||
|
@ -23,4 +25,6 @@ add_compile_definitions(
|
|||
CVAR_PREFIX_DEVELOPER_TOOLS="${CVAR_PREFIX_DEVELOPER_TOOLS}"
|
||||
CVAR_PREFIX_GENERAL="${CVAR_PREFIX_GENERAL}"
|
||||
CVAR_PREFIX_REMOTE="${CVAR_PREFIX_REMOTE}"
|
||||
CVAR_PREFIX_GAMEPLAY_STATS="${CVAR_PREFIX_GAMEPLAY_STATS}"
|
||||
CVAR_PREFIX_TIME_DISPLAY="${CVAR_PREFIX_TIME_DISPLAY}"
|
||||
)
|
|
@ -37,10 +37,10 @@ const static std::vector<std::pair<std::string, const char*>> digitList = {
|
|||
};
|
||||
|
||||
const std::vector<TimeObject> timeDisplayList = {
|
||||
{ DISPLAY_IN_GAME_TIMER, "Display Gameplay Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.InGameTimer") },
|
||||
{ DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_ENHANCEMENT("TimeDisplay.Timers.TimeofDay") },
|
||||
{ DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.HotWater") },
|
||||
{ DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_ENHANCEMENT("TimeDisplay.Timers.NaviTimer") }
|
||||
{ DISPLAY_IN_GAME_TIMER, "Display Gameplay Timer", CVAR_TIME_DISPLAY("Timers.InGameTimer") },
|
||||
{ DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_TIME_DISPLAY("Timers.TimeofDay") },
|
||||
{ DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_TIME_DISPLAY("Timers.HotWater") },
|
||||
{ DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_TIME_DISPLAY("Timers.NaviTimer") }
|
||||
};
|
||||
|
||||
static std::vector<TimeObject> activeTimers;
|
||||
|
@ -227,11 +227,11 @@ void TimeDisplayWindow::Draw() {
|
|||
}
|
||||
|
||||
void TimeDisplayInitSettings() {
|
||||
fontScale = CVarGetFloat(CVAR_ENHANCEMENT("TimeDisplay.FontScale"), 1.0f);
|
||||
fontScale = CVarGetFloat(CVAR_TIME_DISPLAY("FontScale"), 1.0f);
|
||||
if (fontScale < 1.0f) {
|
||||
fontScale = 1.0f;
|
||||
}
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"), 0)) {
|
||||
if (CVarGetInteger(CVAR_TIME_DISPLAY("ShowWindowBG"), 0)) {
|
||||
windowBG = ImVec4(0, 0, 0, 0);
|
||||
} else {
|
||||
windowBG = ImVec4(0, 0, 0, 0.5f);
|
||||
|
|
|
@ -1164,6 +1164,9 @@ const std::vector<FlagTable> flagTables = {
|
|||
{ RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD, "RAND_INF_COLOSSUS_GREAT_FAIRY_REWARD" },
|
||||
{ RAND_INF_OGC_GREAT_FAIRY_REWARD, "RAND_INF_OGC_GREAT_FAIRY_REWARD" },
|
||||
|
||||
{ RAND_INF_ZELDAS_LETTER, "RAND_INF_ZELDAS_LETTER" },
|
||||
{ RAND_INF_WEIRD_EGG, "RAND_INF_WEIRD_EGG" },
|
||||
|
||||
{ RAND_INF_KF_SOUTH_GRASS_WEST_RUPEE, "RAND_INF_KF_SOUTH_GRASS_WEST_RUPEE" },
|
||||
{ RAND_INF_KF_NORTH_GRASS_WEST_RUPEE, "RAND_INF_KF_NORTH_GRASS_WEST_RUPEE" },
|
||||
{ RAND_INF_KF_NORTH_GRASS_EAST_RUPEE, "RAND_INF_KF_NORTH_GRASS_EAST_RUPEE" },
|
||||
|
|
|
@ -12,6 +12,10 @@ std::chrono::duration<double, std::milli> GetPerformanceTimer(TimerID timer){
|
|||
return totalTimes[timer];
|
||||
}
|
||||
|
||||
void ResetPerformanceTimer(TimerID timer) {
|
||||
totalTimes[timer] = {};
|
||||
}
|
||||
|
||||
void ResetPerformanceTimers(){
|
||||
totalTimes = {};
|
||||
}
|
|
@ -25,12 +25,14 @@ typedef enum {
|
|||
PT_TOD_ACCESS,
|
||||
PT_ENTRANCE_LOGIC,
|
||||
PT_LOCATION_LOGIC,
|
||||
PT_RECALCULATE_AVAILABLE_CHECKS,
|
||||
PT_MAX
|
||||
} TimerID;
|
||||
|
||||
void StartPerformanceTimer(TimerID timer);
|
||||
void StopPerformanceTimer(TimerID timer);
|
||||
std::chrono::duration<double, std::milli> GetPerformanceTimer(TimerID timer);
|
||||
void ResetPerformanceTimer(TimerID timer);
|
||||
void ResetPerformanceTimers();
|
||||
static std::array<std::chrono::duration<double, std::milli>, PT_MAX> totalTimes = {};
|
||||
static std::array<std::chrono::high_resolution_clock::time_point, PT_MAX> timeStarted = {};
|
||||
|
|
|
@ -391,7 +391,7 @@ void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color
|
|||
}
|
||||
|
||||
bool compareTimestampInfoByTime(const TimestampInfo& a, const TimestampInfo& b) {
|
||||
return CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"), 0) ? a.time > b.time : a.time < b.time;
|
||||
return CVarGetInteger(CVAR_GAMEPLAY_STATS("ReverseTimestamps"), 0) ? a.time > b.time : a.time < b.time;
|
||||
}
|
||||
|
||||
const char* ResolveSceneID(int sceneID, int roomID){
|
||||
|
@ -452,13 +452,13 @@ void DrawGameplayStatsHeader() {
|
|||
} else {
|
||||
GameplayStatsRow("Total Game Time:", formatTimestampGameplayStat(GAMEPLAYSTAT_TOTAL_TIME), gSaveContext.ship.stats.gameComplete ? COLOR_GREEN : COLOR_WHITE);
|
||||
}
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"), 0)) { // !Only display total game time
|
||||
if (CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowAdditionalTimers"), 0)) { // !Only display total game time
|
||||
GameplayStatsRow("Gameplay Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.playTimer / 2), COLOR_GREY);
|
||||
GameplayStatsRow("Pause Menu Time:", formatTimestampGameplayStat(gSaveContext.ship.stats.pauseTimer / 3), COLOR_GREY);
|
||||
GameplayStatsRow("Time in scene:", formatTimestampGameplayStat(gSaveContext.ship.stats.sceneTimer / 2), COLOR_LIGHT_BLUE);
|
||||
GameplayStatsRow("Time in room:", formatTimestampGameplayStat(gSaveContext.ship.stats.roomTimer / 2), COLOR_LIGHT_BLUE);
|
||||
}
|
||||
if (gPlayState != NULL && CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo"), 0)) { // && display debug info
|
||||
if (gPlayState != NULL && CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowDebugInfo"), 0)) { // && display debug info
|
||||
GameplayStatsRow("play->sceneNum:", formatHexGameplayStat(gPlayState->sceneNum), COLOR_YELLOW);
|
||||
GameplayStatsRow("gSaveContext.entranceIndex:", formatHexGameplayStat(gSaveContext.entranceIndex), COLOR_YELLOW);
|
||||
GameplayStatsRow("gSaveContext.cutsceneIndex:", formatHexOnlyGameplayStat(gSaveContext.cutsceneIndex), COLOR_YELLOW);
|
||||
|
@ -576,13 +576,13 @@ void DrawGameplayStatsBreakdownTab() {
|
|||
for (int i = 0; i < gSaveContext.ship.stats.tsIdx; i++) {
|
||||
std::string sceneName = ResolveSceneID(gSaveContext.ship.stats.sceneTimestamps[i].scene, gSaveContext.ship.stats.sceneTimestamps[i].room);
|
||||
std::string name;
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), 0) && gSaveContext.ship.stats.sceneTimestamps[i].scene != SCENE_GROTTOS) {
|
||||
if (CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) && gSaveContext.ship.stats.sceneTimestamps[i].scene != SCENE_GROTTOS) {
|
||||
name = fmt::format("{:s} Room {:d}", sceneName, gSaveContext.ship.stats.sceneTimestamps[i].room);
|
||||
} else {
|
||||
name = sceneName;
|
||||
}
|
||||
strcpy(sceneTimestampDisplay[i].name, name.c_str());
|
||||
sceneTimestampDisplay[i].time = CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), 0) ?
|
||||
sceneTimestampDisplay[i].time = CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) ?
|
||||
gSaveContext.ship.stats.sceneTimestamps[i].roomTime : gSaveContext.ship.stats.sceneTimestamps[i].sceneTime;
|
||||
sceneTimestampDisplay[i].color = COLOR_GREY;
|
||||
sceneTimestampDisplay[i].isRoom = gSaveContext.ship.stats.sceneTimestamps[i].isRoom;
|
||||
|
@ -593,13 +593,13 @@ void DrawGameplayStatsBreakdownTab() {
|
|||
ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch);
|
||||
for (int i = 0; i < gSaveContext.ship.stats.tsIdx; i++) {
|
||||
TimestampInfo tsInfo = sceneTimestampDisplay[i];
|
||||
bool canShow = !tsInfo.isRoom || CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), 0);
|
||||
bool canShow = !tsInfo.isRoom || CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0);
|
||||
if (tsInfo.time > 0 && strnlen(tsInfo.name, 40) > 1 && canShow) {
|
||||
GameplayStatsRow(tsInfo.name, formatTimestampGameplayStat(tsInfo.time), tsInfo.color);
|
||||
}
|
||||
}
|
||||
std::string toPass;
|
||||
if (CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), 0) && gSaveContext.ship.stats.sceneNum != SCENE_GROTTOS) {
|
||||
if (CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) && gSaveContext.ship.stats.sceneNum != SCENE_GROTTOS) {
|
||||
toPass = fmt::format("{:s} Room {:d}", ResolveSceneID(gSaveContext.ship.stats.sceneNum, gSaveContext.ship.stats.roomNum), gSaveContext.ship.stats.roomNum);
|
||||
} else {
|
||||
toPass = ResolveSceneID(gSaveContext.ship.stats.sceneNum, gSaveContext.ship.stats.roomNum);
|
||||
|
@ -610,27 +610,27 @@ void DrawGameplayStatsBreakdownTab() {
|
|||
}
|
||||
|
||||
void DrawGameplayStatsOptionsTab() {
|
||||
UIWidgets::CVarCheckbox("Show in-game total timer", CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"),
|
||||
UIWidgets::CVarCheckbox("Show in-game total timer", CVAR_GAMEPLAY_STATS("ShowIngameTimer"),
|
||||
UIWidgets::CheckboxOptions()
|
||||
.Tooltip("Keep track of the timer as an in-game HUD element. The position of the "
|
||||
"timer can be changed in the Cosmetics Editor.")
|
||||
.Color(THEME_COLOR));
|
||||
UIWidgets::CVarCheckbox("Show latest timestamps on top", CVAR_ENHANCEMENT("GameplayStats.ReverseTimestamps"),
|
||||
UIWidgets::CVarCheckbox("Show latest timestamps on top", CVAR_GAMEPLAY_STATS("ReverseTimestamps"),
|
||||
UIWidgets::CheckboxOptions().Color(THEME_COLOR));
|
||||
UIWidgets::CVarCheckbox("Room Breakdown", CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"),
|
||||
UIWidgets::CVarCheckbox("Room Breakdown", CVAR_GAMEPLAY_STATS("RoomBreakdown"),
|
||||
UIWidgets::CheckboxOptions()
|
||||
.Tooltip("Allows a more in-depth perspective of time spent in a certain map.")
|
||||
.Color(THEME_COLOR));
|
||||
UIWidgets::CVarCheckbox("RTA Timing on new files", CVAR_ENHANCEMENT("GameplayStats.RTATiming"),
|
||||
UIWidgets::CVarCheckbox("RTA Timing on new files", CVAR_GAMEPLAY_STATS("RTATiming"),
|
||||
UIWidgets::CheckboxOptions()
|
||||
.Tooltip("Timestamps are relative to starting timestamp rather than in game time, "
|
||||
"usually necessary for races/speedruns.\n\n"
|
||||
"Starting timestamp is on first non-C-up input after intro cutscene.\n\n"
|
||||
"NOTE: THIS NEEDS TO BE SET BEFORE CREATING A FILE TO TAKE EFFECT")
|
||||
.Color(THEME_COLOR));
|
||||
UIWidgets::CVarCheckbox("Show additional detail timers", CVAR_ENHANCEMENT("GameplayStats.ShowAdditionalTimers"),
|
||||
UIWidgets::CVarCheckbox("Show additional detail timers", CVAR_GAMEPLAY_STATS("ShowAdditionalTimers"),
|
||||
UIWidgets::CheckboxOptions().Color(THEME_COLOR));
|
||||
UIWidgets::CVarCheckbox("Show Debug Info", CVAR_ENHANCEMENT("GameplayStats.ShowDebugInfo"),
|
||||
UIWidgets::CVarCheckbox("Show Debug Info", CVAR_GAMEPLAY_STATS("ShowDebugInfo"),
|
||||
UIWidgets::CheckboxOptions().Color(THEME_COLOR));
|
||||
}
|
||||
|
||||
|
@ -671,7 +671,7 @@ void InitStats(bool isDebug) {
|
|||
for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.ship.stats.dungeonKeys); dungeon++) {
|
||||
gSaveContext.ship.stats.dungeonKeys[dungeon] = isDebug ? 8 : 0;
|
||||
}
|
||||
gSaveContext.ship.stats.rtaTiming = CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RTATiming"), 0);
|
||||
gSaveContext.ship.stats.rtaTiming = CVarGetInteger(CVAR_GAMEPLAY_STATS("RTATiming"), 0);
|
||||
gSaveContext.ship.stats.fileCreatedAt = 0;
|
||||
gSaveContext.ship.stats.playTimer = 0;
|
||||
gSaveContext.ship.stats.pauseTimer = 0;
|
||||
|
|
|
@ -27,7 +27,7 @@ extern "C" {
|
|||
: gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED])) \
|
||||
:\
|
||||
(gSaveContext.ship.stats.playTimer / 2 + gSaveContext.ship.stats.pauseTimer / 3))
|
||||
#define CURRENT_MODE_TIMER (CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.RoomBreakdown"), 0) ?\
|
||||
#define CURRENT_MODE_TIMER (CVarGetInteger(CVAR_GAMEPLAY_STATS("RoomBreakdown"), 0) ?\
|
||||
gSaveContext.ship.stats.roomTimer :\
|
||||
gSaveContext.ship.stats.sceneTimer)
|
||||
|
||||
|
|
|
@ -402,7 +402,13 @@ bool AddCheckToLogic(LocationAccess& locPair, GetAccessibleLocationsStruct& gals
|
|||
Rando::ItemLocation* location = ctx->GetItemLocation(loc);
|
||||
RandomizerGet locItem = location->GetPlacedRandomizerGet();
|
||||
|
||||
if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion)) {
|
||||
if (!location->IsAddedToPool() && locPair.ConditionsMet(parentRegion, gals.calculatingAvailableChecks)) {
|
||||
if (gals.calculatingAvailableChecks) {
|
||||
gals.accessibleLocations.push_back(loc);
|
||||
StopPerformanceTimer(PT_LOCATION_LOGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
location->AddToPool();
|
||||
|
||||
if (locItem == RG_NONE) {
|
||||
|
@ -498,19 +504,23 @@ void ProcessRegion(Region* region, GetAccessibleLocationsStruct& gals, Randomize
|
|||
}
|
||||
|
||||
// Return any of the targetLocations that are accessible in logic
|
||||
std::vector<RandomizerCheck> ReachabilitySearch(const std::vector<RandomizerCheck>& targetLocations, RandomizerGet ignore /* = RG_NONE*/) {
|
||||
std::vector<RandomizerCheck> ReachabilitySearch(const std::vector<RandomizerCheck>& targetLocations, RandomizerGet ignore /* = RG_NONE*/, bool calculatingAvailableChecks /* = false */) {
|
||||
auto ctx = Rando::Context::GetInstance();
|
||||
GetAccessibleLocationsStruct gals(0);
|
||||
ResetLogic(ctx, gals, true);
|
||||
gals.calculatingAvailableChecks = calculatingAvailableChecks;
|
||||
ResetLogic(ctx, gals, !calculatingAvailableChecks);
|
||||
do {
|
||||
gals.InitLoop();
|
||||
for (size_t i = 0; i < gals.regionPool.size(); i++) {
|
||||
ProcessRegion(RegionTable(gals.regionPool[i]), gals, ignore);
|
||||
}
|
||||
} while (gals.logicUpdated);
|
||||
erase_if(gals.accessibleLocations, [&targetLocations, ctx](RandomizerCheck loc){
|
||||
erase_if(gals.accessibleLocations, [&targetLocations, ctx, calculatingAvailableChecks](RandomizerCheck loc) {
|
||||
if (ctx->GetItemLocation(loc)->GetPlacedRandomizerGet() != RG_NONE && !calculatingAvailableChecks) {
|
||||
return false;
|
||||
}
|
||||
for (RandomizerCheck allowedLocation : targetLocations) {
|
||||
if (loc == allowedLocation || ctx->GetItemLocation(loc)->GetPlacedRandomizerGet() != RG_NONE) {
|
||||
if (loc == allowedLocation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ struct GetAccessibleLocationsStruct {
|
|||
std::vector<RandomizerCheck> itemSphere;
|
||||
std::list<Rando::Entrance*> entranceSphere;
|
||||
|
||||
bool calculatingAvailableChecks = false;
|
||||
|
||||
GetAccessibleLocationsStruct(int _maxGsCount){
|
||||
regionPool = {RR_ROOT};
|
||||
gsCount = 0;
|
||||
|
@ -62,7 +64,7 @@ std::vector<RandomizerCheck> GetEmptyLocations(std::vector<RandomizerCheck> allo
|
|||
void ProcessRegion(Region* region, GetAccessibleLocationsStruct& gals, RandomizerGet ignore = RG_NONE,
|
||||
bool stopOnBeatable = false, bool addToPlaythrough = false);
|
||||
|
||||
std::vector<RandomizerCheck> ReachabilitySearch(const std::vector<RandomizerCheck>& allowedLocations, RandomizerGet ignore=RG_NONE);
|
||||
std::vector<RandomizerCheck> ReachabilitySearch(const std::vector<RandomizerCheck>& allowedLocations, RandomizerGet ignore=RG_NONE, bool calculatingAvailableChecks=false);
|
||||
|
||||
void GeneratePlaythrough();
|
||||
|
||||
|
|
|
@ -236,6 +236,14 @@ void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) {
|
|||
Flags_UnsetRandomizerInf(RAND_INF_CHILD_TRADES_HAS_CHICKEN);
|
||||
}
|
||||
|
||||
if (flagType == FLAG_EVENT_CHECK_INF && flag == EVENTCHKINF_OBTAINED_ZELDAS_LETTER) {
|
||||
Flags_SetRandomizerInf(RAND_INF_ZELDAS_LETTER);
|
||||
}
|
||||
|
||||
if (flagType == FLAG_EVENT_CHECK_INF && flag == EVENTCHKINF_OBTAINED_POCKET_EGG) {
|
||||
Flags_SetRandomizerInf(RAND_INF_WEIRD_EGG);
|
||||
}
|
||||
|
||||
RandomizerCheck rc = GetRandomizerCheckFromFlag(flagType, flag);
|
||||
if (rc == RC_UNKNOWN_CHECK) return;
|
||||
|
||||
|
@ -351,6 +359,7 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) {
|
|||
loc->SetCheckStatus(RCSHOW_COLLECTED);
|
||||
CheckTracker::SpoilAreaFromCheck(randomizerQueuedCheck);
|
||||
CheckTracker::RecalculateAllAreaTotals();
|
||||
CheckTracker::RecalculateAvailableChecks();
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, SECTION_ID_TRACKER_DATA, true);
|
||||
randomizerQueuedCheck = RC_UNKNOWN_CHECK;
|
||||
randomizerQueuedItemEntry = GET_ITEM_NONE;
|
||||
|
|
|
@ -228,5 +228,14 @@ void ItemLocation::ResetVariables() {
|
|||
areas = {};
|
||||
status = RCSHOW_UNCHECKED;
|
||||
isSkipped = false;
|
||||
isAvailable = false;
|
||||
}
|
||||
|
||||
bool ItemLocation::IsAvailable() const {
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
void ItemLocation::SetAvailable(bool isAvailable_) {
|
||||
isAvailable = isAvailable_;
|
||||
}
|
||||
}
|
|
@ -56,6 +56,8 @@ class ItemLocation {
|
|||
bool IsFoolishCandidate() const;
|
||||
void SetBarrenCandidate();
|
||||
void ResetVariables();
|
||||
bool IsAvailable() const;
|
||||
void SetAvailable(bool isAvailable_);
|
||||
|
||||
private:
|
||||
RandomizerCheck rc;
|
||||
|
@ -76,5 +78,6 @@ class ItemLocation {
|
|||
bool barrenCandidate = false;
|
||||
RandomizerCheckStatus status = RCSHOW_UNCHECKED;
|
||||
bool isSkipped = false;
|
||||
bool isAvailable = false;
|
||||
};
|
||||
} // namespace Rando
|
|
@ -11,6 +11,11 @@
|
|||
|
||||
#include <fstream>
|
||||
|
||||
extern "C" {
|
||||
extern SaveContext gSaveContext;
|
||||
extern PlayState* gPlayState;
|
||||
}
|
||||
|
||||
//generic grotto event list
|
||||
std::vector<EventAccess> grottoEvents;
|
||||
|
||||
|
@ -27,7 +32,7 @@ bool LocationAccess::CheckConditionAtAgeTime(bool& age, bool& time) const {
|
|||
return GetConditionsMet();
|
||||
}
|
||||
|
||||
bool LocationAccess::ConditionsMet(Region* parentRegion) const {
|
||||
bool LocationAccess::ConditionsMet(Region* parentRegion, bool calculatingAvailableChecks) const {
|
||||
//WARNING enterance validation can run this after resetting the access for sphere 0 validation
|
||||
//When refactoring ToD access, either fix the above or do not assume that we
|
||||
//have any access at all just because this is being run
|
||||
|
@ -41,8 +46,8 @@ bool LocationAccess::ConditionsMet(Region* parentRegion) const {
|
|||
) {
|
||||
conditionsMet = true;
|
||||
}
|
||||
|
||||
return conditionsMet && CanBuy();
|
||||
|
||||
return conditionsMet && (calculatingAvailableChecks || CanBuy()); // TODO: run CanBuy when price is known due to settings
|
||||
}
|
||||
|
||||
bool LocationAccess::CanBuy() const {
|
||||
|
@ -224,8 +229,73 @@ bool MQSpiritSharedBrokenWallRoom(const RandomizerRegion region, ConditionFn con
|
|||
return areaTable[region].MQSpiritShared(condition, true, anyAge);
|
||||
}
|
||||
|
||||
bool BeanPlanted(const RandomizerRegion region) {
|
||||
// swchFlag found using the Actor Viewer to get the Obj_Bean parameters & 0x3F
|
||||
// not tested with multiple OTRs, but can be automated similarly to GetDungeonSmallKeyDoors
|
||||
SceneID sceneID;
|
||||
uint8_t swchFlag;
|
||||
switch (region) {
|
||||
case RR_ZORAS_RIVER:
|
||||
sceneID = SceneID::SCENE_ZORAS_RIVER;
|
||||
swchFlag = 3;
|
||||
break;
|
||||
case RR_THE_GRAVEYARD:
|
||||
sceneID = SceneID::SCENE_GRAVEYARD;
|
||||
swchFlag = 3;
|
||||
break;
|
||||
case RR_KOKIRI_FOREST:
|
||||
sceneID = SceneID::SCENE_KOKIRI_FOREST;
|
||||
swchFlag = 9;
|
||||
break;
|
||||
case RR_THE_LOST_WOODS:
|
||||
sceneID = SceneID::SCENE_LOST_WOODS;
|
||||
swchFlag = 4;
|
||||
break;
|
||||
case RR_LW_BEYOND_MIDO:
|
||||
sceneID = SceneID::SCENE_LOST_WOODS;
|
||||
swchFlag = 18;
|
||||
break;
|
||||
case RR_DEATH_MOUNTAIN_TRAIL:
|
||||
sceneID = SceneID::SCENE_DEATH_MOUNTAIN_TRAIL;
|
||||
swchFlag = 6;
|
||||
break;
|
||||
case RR_LAKE_HYLIA:
|
||||
sceneID = SceneID::SCENE_LAKE_HYLIA;
|
||||
swchFlag = 1;
|
||||
break;
|
||||
case RR_GERUDO_VALLEY:
|
||||
sceneID = SceneID::SCENE_GERUDO_VALLEY;
|
||||
swchFlag = 3;
|
||||
break;
|
||||
case RR_DMC_CENTRAL_LOCAL:
|
||||
sceneID = SceneID::SCENE_DEATH_MOUNTAIN_CRATER;
|
||||
swchFlag = 3;
|
||||
break;
|
||||
case RR_DESERT_COLOSSUS:
|
||||
sceneID = SceneID::SCENE_DESERT_COLOSSUS;
|
||||
swchFlag = 24;
|
||||
break;
|
||||
default:
|
||||
sceneID = SCENE_ID_MAX;
|
||||
swchFlag = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the swch value for the scene
|
||||
uint32_t swch;
|
||||
if (gPlayState != nullptr && gPlayState->sceneNum == sceneID) {
|
||||
swch = gPlayState->actorCtx.flags.swch;
|
||||
} else if (sceneID != SCENE_ID_MAX) {
|
||||
swch = gSaveContext.sceneFlags[sceneID].swch;
|
||||
} else {
|
||||
swch = 0;
|
||||
}
|
||||
|
||||
return swch >> swchFlag & 1;
|
||||
}
|
||||
|
||||
bool CanPlantBean(const RandomizerRegion region) {
|
||||
return areaTable[region].CanPlantBeanCheck();
|
||||
return areaTable[region].CanPlantBeanCheck() || BeanPlanted(region);
|
||||
}
|
||||
|
||||
bool BothAges(const RandomizerRegion region) {
|
||||
|
|
|
@ -75,7 +75,7 @@ class LocationAccess {
|
|||
|
||||
bool CheckConditionAtAgeTime(bool& age, bool& time) const;
|
||||
|
||||
bool ConditionsMet(Region* parentRegion) const;
|
||||
bool ConditionsMet(Region* parentRegion, bool calculatingAvailableChecks) const;
|
||||
|
||||
RandomizerCheck GetLocation() const {
|
||||
return location;
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
#include "macros.h"
|
||||
#include "variables.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include "StringHelper.h"
|
||||
#include "soh/resource/type/Scene.h"
|
||||
#include "soh/resource/type/scenecommand/SetTransitionActorList.h"
|
||||
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
|
||||
#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h"
|
||||
|
||||
namespace Rando {
|
||||
|
||||
|
@ -87,7 +92,7 @@ namespace Rando {
|
|||
case RG_BOMB_BAG:
|
||||
return CurrentUpgrade(UPG_BOMB_BAG);
|
||||
case RG_MAGIC_SINGLE:
|
||||
return GetSaveContext()->magicLevel >= 1;
|
||||
return GetSaveContext()->magicLevel >= 1 || GetSaveContext()->isMagicAcquired;
|
||||
// Songs
|
||||
case RG_ZELDAS_LULLABY:
|
||||
case RG_EPONAS_SONG:
|
||||
|
@ -217,6 +222,7 @@ namespace Rando {
|
|||
case RG_GOLDEN_SCALE:
|
||||
return CurrentUpgrade(UPG_SCALE) >= 2;
|
||||
case RG_POCKET_EGG:
|
||||
return CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG);
|
||||
case RG_COJIRO:
|
||||
case RG_ODD_MUSHROOM:
|
||||
case RG_ODD_POTION:
|
||||
|
@ -226,7 +232,7 @@ namespace Rando {
|
|||
case RG_EYEBALL_FROG:
|
||||
case RG_EYEDROPS:
|
||||
case RG_CLAIM_CHECK:
|
||||
return CheckRandoInf(itemName - RG_POCKET_EGG + RAND_INF_ADULT_TRADES_HAS_POCKET_EGG);
|
||||
return CheckRandoInf(itemName - RG_COJIRO + RAND_INF_ADULT_TRADES_HAS_COJIRO);
|
||||
case RG_BOTTLE_WITH_BIG_POE:
|
||||
case RG_BOTTLE_WITH_BLUE_FIRE:
|
||||
case RG_BOTTLE_WITH_BLUE_POTION:
|
||||
|
@ -1509,6 +1515,8 @@ namespace Rando {
|
|||
mSaveContext->isDoubleDefenseAcquired = state;
|
||||
break;
|
||||
case RG_POCKET_EGG:
|
||||
SetRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG, state);
|
||||
break;
|
||||
case RG_COJIRO:
|
||||
case RG_ODD_MUSHROOM:
|
||||
case RG_ODD_POTION:
|
||||
|
@ -1518,7 +1526,7 @@ namespace Rando {
|
|||
case RG_EYEBALL_FROG:
|
||||
case RG_EYEDROPS:
|
||||
case RG_CLAIM_CHECK:
|
||||
SetRandoInf(randoGet - RG_POCKET_EGG + RAND_INF_ADULT_TRADES_HAS_POCKET_EGG, state);
|
||||
SetRandoInf(randoGet - RG_COJIRO + RAND_INF_ADULT_TRADES_HAS_COJIRO, state);
|
||||
break;
|
||||
case RG_PROGRESSIVE_HOOKSHOT:
|
||||
{
|
||||
|
@ -2096,8 +2104,97 @@ namespace Rando {
|
|||
}
|
||||
}
|
||||
|
||||
// Get the swch bit positions for the dungeon
|
||||
const std::vector<uint8_t>& GetDungeonSmallKeyDoors(SceneID sceneId) {
|
||||
static const std::vector<uint8_t> emptyVector;
|
||||
|
||||
auto dungeonInfo = Rando::Context::GetInstance()->GetDungeons()->GetDungeonFromScene(sceneId);
|
||||
if (dungeonInfo == nullptr) {
|
||||
return emptyVector;
|
||||
}
|
||||
|
||||
bool masterQuest = dungeonInfo->IsMQ();
|
||||
|
||||
// Create a unique key for the dungeon and master quest
|
||||
uint8_t key = sceneId | (masterQuest << 7);
|
||||
|
||||
static std::unordered_map<uint8_t, std::vector<uint8_t>> dungeonSmallKeyDoors;
|
||||
auto foundEntry = dungeonSmallKeyDoors.find(key);
|
||||
if (foundEntry != dungeonSmallKeyDoors.end()) {
|
||||
return foundEntry->second;
|
||||
}
|
||||
dungeonSmallKeyDoors[key] = {};
|
||||
|
||||
// Get the scene path
|
||||
SceneTableEntry* sceneTableEntry = &gSceneTable[sceneId];
|
||||
std::string scenePath = StringHelper::Sprintf("scenes/%s/%s/%s", masterQuest ? "mq" : "nonmq",
|
||||
sceneTableEntry->sceneFile.fileName, sceneTableEntry->sceneFile.fileName);
|
||||
|
||||
// Load the scene
|
||||
std::shared_ptr<SOH::Scene> scene = std::dynamic_pointer_cast<SOH::Scene>(
|
||||
Ship::Context::GetInstance()->GetResourceManager()->LoadResource(scenePath));
|
||||
if (scene == nullptr) {
|
||||
return emptyVector;
|
||||
}
|
||||
|
||||
// Find the SetTransitionActorList command
|
||||
std::shared_ptr<SOH::SetTransitionActorList> transitionActorListCommand = nullptr;
|
||||
for (auto& command : scene->commands) {
|
||||
if (command->cmdId == SOH::SceneCommandID::SetTransitionActorList) {
|
||||
transitionActorListCommand = std::dynamic_pointer_cast<SOH::SetTransitionActorList>(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (transitionActorListCommand == nullptr) {
|
||||
return emptyVector;
|
||||
}
|
||||
|
||||
// Find the bit position for the small key doors
|
||||
for (auto& transitionActor : transitionActorListCommand->transitionActorList) {
|
||||
if (transitionActor.id == ACTOR_EN_DOOR) {
|
||||
uint8_t doorType = (transitionActor.params >> 7) & 7;
|
||||
if (doorType == DOOR_LOCKED) {
|
||||
dungeonSmallKeyDoors[key].emplace_back(transitionActor.params & 0x3F);
|
||||
}
|
||||
} else if (transitionActor.id == ACTOR_DOOR_SHUTTER) {
|
||||
uint8_t doorType = (transitionActor.params >> 7) & 15;
|
||||
if (doorType == SHUTTER_BACK_LOCKED || doorType == SHUTTER_BOSS || doorType == SHUTTER_KEY_LOCKED) {
|
||||
dungeonSmallKeyDoors[key].emplace_back(transitionActor.params & 0x3F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dungeonSmallKeyDoors[key];
|
||||
}
|
||||
|
||||
int8_t GetUsedSmallKeyCount(SceneID sceneId) {
|
||||
const auto& smallKeyDoors = GetDungeonSmallKeyDoors(sceneId);
|
||||
|
||||
// Get the swch value for the scene
|
||||
uint32_t swch;
|
||||
if (gPlayState != nullptr && gPlayState->sceneNum == sceneId) {
|
||||
swch = gPlayState->actorCtx.flags.swch;
|
||||
} else {
|
||||
swch = gSaveContext.sceneFlags[sceneId].swch;
|
||||
}
|
||||
|
||||
// Count the number of small keys doors unlocked
|
||||
int8_t unlockedSmallKeyDoors = 0;
|
||||
for (auto& smallKeyDoor : smallKeyDoors) {
|
||||
unlockedSmallKeyDoors += swch >> smallKeyDoor & 1;
|
||||
}
|
||||
|
||||
// RANDOTODO: Account for MQ Water trick that causes the basement lock to unlock when the player clears the stalfos pit.
|
||||
return unlockedSmallKeyDoors;
|
||||
}
|
||||
|
||||
uint8_t Logic::GetSmallKeyCount(uint32_t dungeonIndex) {
|
||||
return mSaveContext->inventory.dungeonKeys[dungeonIndex];
|
||||
int8_t dungeonKeys = mSaveContext->inventory.dungeonKeys[dungeonIndex];
|
||||
if (dungeonKeys == -1) {
|
||||
// never got keys, so can't have used keys
|
||||
return 0;
|
||||
}
|
||||
return dungeonKeys + GetUsedSmallKeyCount(SceneID(dungeonIndex));
|
||||
}
|
||||
|
||||
void Logic::SetSmallKeyCount(uint32_t dungeonIndex, uint8_t count) {
|
||||
|
@ -2172,7 +2269,8 @@ namespace Rando {
|
|||
IsKeysanity = ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) ||
|
||||
ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) ||
|
||||
ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_ANYWHERE);
|
||||
AmmoCanDrop = /*AmmoDrops.IsNot(AMMODROPS_NONE) TODO: AmmoDrop setting*/ true;
|
||||
|
||||
//AmmoCanDrop = /*AmmoDrops.IsNot(AMMODROPS_NONE)*/ false; TODO: AmmoDrop setting
|
||||
|
||||
//Child item logic
|
||||
SkullMask = false;
|
||||
|
|
|
@ -90,7 +90,7 @@ class Logic {
|
|||
bool FairyPot = false;
|
||||
bool FreeFairies = false;
|
||||
bool FairyPond = false;
|
||||
bool AmmoCanDrop = false;
|
||||
bool AmmoCanDrop = true;
|
||||
|
||||
uint8_t PieceOfHeart = 0;
|
||||
uint8_t HeartContainer = 0;
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
#include "soh/SohGui/UIWidgets.hpp"
|
||||
#include "soh/SohGui/SohGui.hpp"
|
||||
#include "dungeon.h"
|
||||
#include "entrance.h"
|
||||
#include "location_access.h"
|
||||
#include "3drando/fill.hpp"
|
||||
#include "soh/Enhancements/debugger/performanceTimer.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -84,7 +87,7 @@ bool fishsanityAgeSplit;
|
|||
bool initialized;
|
||||
bool doAreaScroll;
|
||||
bool previousShowHidden = false;
|
||||
bool hideShopUnshuffledChecks = true;
|
||||
bool hideShopUnshuffledChecks = false;
|
||||
bool alwaysShowGS = false;
|
||||
|
||||
std::map<uint32_t, RandomizerCheck> startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 },
|
||||
|
@ -132,8 +135,10 @@ bool areasFullyChecked[RCAREA_INVALID];
|
|||
u32 areasSpoiled = 0;
|
||||
bool showVOrMQ;
|
||||
s8 areaChecksGotten[RCAREA_INVALID]; //| "Kokiri Forest (4/9)"
|
||||
s8 areaChecksAvailable[RCAREA_INVALID];
|
||||
s8 areaCheckTotals[RCAREA_INVALID];
|
||||
uint16_t totalChecks = 0;
|
||||
uint16_t totalChecksAvailable = 0;
|
||||
uint16_t totalChecksGotten = 0;
|
||||
bool optCollapseAll; // A bool that will collapse all checks once
|
||||
bool optExpandAll; // A bool that will expand all checks once
|
||||
|
@ -166,6 +171,8 @@ bool hideCollected = false;
|
|||
bool showHidden = true;
|
||||
bool mystery = false;
|
||||
bool showLogicTooltip = false;
|
||||
bool enableAvailableChecks = false;
|
||||
bool onlyShowAvailable = false;
|
||||
|
||||
SceneID DungeonSceneLookupByArea(RandomizerCheckArea area) {
|
||||
switch (area) {
|
||||
|
@ -235,10 +242,12 @@ void TrySetAreas() {
|
|||
|
||||
void CalculateTotals() {
|
||||
totalChecks = 0;
|
||||
totalChecksAvailable = 0;
|
||||
totalChecksGotten = 0;
|
||||
|
||||
for (uint8_t i = 0; i < RCAREA_INVALID; i++) {
|
||||
totalChecks += areaCheckTotals[i];
|
||||
totalChecksAvailable += areaChecksAvailable[i];
|
||||
totalChecksGotten += areaChecksGotten[i];
|
||||
}
|
||||
}
|
||||
|
@ -251,17 +260,43 @@ uint16_t GetTotalChecksGotten() {
|
|||
return totalChecksGotten;
|
||||
}
|
||||
|
||||
bool IsCheckHidden(RandomizerCheck rc) {
|
||||
Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
RandomizerCheckStatus status = itemLocation->GetCheckStatus();
|
||||
bool available = itemLocation->IsAvailable();
|
||||
bool skipped = itemLocation->GetIsSkipped();
|
||||
bool obtained = itemLocation->HasObtained();
|
||||
bool seen = status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED;
|
||||
bool scummed = status == RCSHOW_SCUMMED;
|
||||
bool unchecked = status == RCSHOW_UNCHECKED;
|
||||
|
||||
return !showHidden && (
|
||||
(skipped && hideSkipped) ||
|
||||
(seen && hideSeen) ||
|
||||
(scummed && hideScummed) ||
|
||||
(unchecked && hideUnchecked)
|
||||
);
|
||||
}
|
||||
|
||||
void RecalculateAreaTotals(RandomizerCheckArea rcArea) {
|
||||
areaChecksGotten[rcArea] = 0;
|
||||
areaChecksAvailable[rcArea] = 0;
|
||||
areaCheckTotals[rcArea] = 0;
|
||||
for (auto rc : checksByArea.at(rcArea)) {
|
||||
if (!IsVisibleInCheckTracker(rc)) {
|
||||
continue;
|
||||
}
|
||||
areaCheckTotals[rcArea]++;
|
||||
if (OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetIsSkipped() || OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->HasObtained()) {
|
||||
|
||||
Rando::ItemLocation* itemLoc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
|
||||
if (itemLoc->GetIsSkipped() || itemLoc->HasObtained()) {
|
||||
areaChecksGotten[rcArea]++;
|
||||
}
|
||||
|
||||
if (itemLoc->IsAvailable() && !IsCheckHidden(rc)) {
|
||||
areaChecksAvailable[rcArea]++;
|
||||
}
|
||||
}
|
||||
CalculateTotals();
|
||||
}
|
||||
|
@ -308,6 +343,7 @@ void SetCheckCollected(RandomizerCheck rc) {
|
|||
if (IsVisibleInCheckTracker(rc)) {
|
||||
if (!OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->GetIsSkipped()) {
|
||||
areaChecksGotten[loc->GetArea()]++;
|
||||
areaChecksAvailable[loc->GetArea()]--;
|
||||
} else {
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false);
|
||||
}
|
||||
|
@ -424,10 +460,12 @@ void ClearAreaChecksAndTotals() {
|
|||
for (auto& [rcArea, vec] : checksByArea) {
|
||||
vec.clear();
|
||||
areaChecksGotten[rcArea] = 0;
|
||||
areaChecksAvailable[rcArea] = 0;
|
||||
areaCheckTotals[rcArea] = 0;
|
||||
}
|
||||
totalChecks = 0;
|
||||
totalChecksGotten = 0;
|
||||
totalChecksAvailable = 0;
|
||||
}
|
||||
|
||||
void SetShopSeen(uint32_t sceneNum, bool prices) {
|
||||
|
@ -469,6 +507,9 @@ void CheckTrackerLoadGame(int32_t fileNum) {
|
|||
if (loc->GetCheckStatus() == RCSHOW_SAVED || loc->GetIsSkipped()) {
|
||||
areaChecksGotten[entry2->GetArea()]++;
|
||||
}
|
||||
if (loc->IsAvailable()) {
|
||||
areaChecksAvailable[entry2->GetArea()]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (areaChecksGotten[entry2->GetArea()] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2->GetArea()) ||
|
||||
|
@ -524,6 +565,7 @@ void CheckTrackerLoadGame(int32_t fileNum) {
|
|||
UpdateAllOrdering();
|
||||
UpdateInventoryChecks();
|
||||
UpdateFilters();
|
||||
RecalculateAvailableChecks();
|
||||
}
|
||||
|
||||
void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) {
|
||||
|
@ -539,6 +581,7 @@ void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) {
|
|||
if (status == RCSHOW_SEEN) {
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(slot)->SetCheckStatus(RCSHOW_IDENTIFIED);
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
|
||||
RecalculateAvailableChecks();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,6 +855,9 @@ void SaveTrackerData(SaveContext* saveContext, int sectionID, bool fullSave) {
|
|||
|
||||
void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
|
||||
SaveTrackerData(saveContext, sectionID, fullSave);
|
||||
if (fullSave) {
|
||||
RecalculateAvailableChecks();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadFile() {
|
||||
|
@ -882,6 +928,8 @@ void CheckTrackerWindow::DrawElement() {
|
|||
showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0);
|
||||
mystery = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("MysteriousShuffle"), 0);
|
||||
showLogicTooltip = CVarGetInteger(CVAR_TRACKER_CHECK("ShowLogic"), 0);
|
||||
enableAvailableChecks = CVarGetInteger(CVAR_TRACKER_CHECK("EnableAvailableChecks"), 0);
|
||||
onlyShowAvailable = CVarGetInteger(CVAR_TRACKER_CHECK("OnlyShowAvailable"), 0);
|
||||
|
||||
hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 0);
|
||||
alwaysShowGS = CVarGetInteger(CVAR_TRACKER_CHECK("AlwaysShowGSLocs"), 0);
|
||||
|
@ -932,9 +980,21 @@ void CheckTrackerWindow::DrawElement() {
|
|||
|
||||
ImGui::TableNextRow(0, headerHeight);
|
||||
ImGui::TableNextColumn();
|
||||
UIWidgets::CVarCheckbox(
|
||||
"Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), UIWidgets::CheckboxOptions({{ .tooltip = "When active, items will show hidden checks by default when updated to this state." }})
|
||||
.Color(THEME_COLOR));
|
||||
if (UIWidgets::CVarCheckbox(
|
||||
"Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), UIWidgets::CheckboxOptions({{.tooltip = "When active, items will show hidden checks by default when updated to this state." }})
|
||||
.Color(THEME_COLOR))) {
|
||||
doAreaScroll = true;
|
||||
showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0);
|
||||
RecalculateAllAreaTotals();
|
||||
}
|
||||
if (enableAvailableChecks) {
|
||||
if (UIWidgets::CVarCheckbox(
|
||||
"Only Show Available Checks", CVAR_TRACKER_CHECK("OnlyShowAvailable"), UIWidgets::CheckboxOptions({{ .tooltip = "When active, unavailable checks will be hidden." }})
|
||||
.Color(THEME_COLOR))) {
|
||||
doAreaScroll = true;
|
||||
RecalculateAllAreaTotals();
|
||||
}
|
||||
}
|
||||
UIWidgets::PaddedSeparator();
|
||||
if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) {
|
||||
optCollapseAll = false;
|
||||
|
@ -960,7 +1020,13 @@ void CheckTrackerWindow::DrawElement() {
|
|||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Total Checks: %d / %d", totalChecksGotten, totalChecks);
|
||||
std::ostringstream totalChecksSS;
|
||||
totalChecksSS << "Total Checks: ";
|
||||
if (enableAvailableChecks) {
|
||||
totalChecksSS << totalChecksAvailable << " Available / ";
|
||||
}
|
||||
totalChecksSS << totalChecksGotten << " Checked / " << totalChecks << " Total";
|
||||
ImGui::Text(totalChecksSS.str().c_str());
|
||||
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
||||
|
@ -1012,7 +1078,8 @@ void CheckTrackerWindow::DrawElement() {
|
|||
doAreaScroll = true;
|
||||
}
|
||||
if ((shouldHideFilteredAreas && filterAreasHidden[rcArea]) ||
|
||||
(!showHidden && ((hideComplete && thisAreaFullyChecked) || (hideIncomplete && !thisAreaFullyChecked)))
|
||||
(!showHidden && ((hideComplete && thisAreaFullyChecked) || (hideIncomplete && !thisAreaFullyChecked))) ||
|
||||
(enableAvailableChecks && onlyShowAvailable && areaChecksAvailable[rcArea] == 0)
|
||||
) {
|
||||
doDraw = false;
|
||||
} else {
|
||||
|
@ -1051,14 +1118,27 @@ void CheckTrackerWindow::DrawElement() {
|
|||
isThisAreaSpoiled = IsAreaSpoiled(rcArea) || mqSpoilers;
|
||||
|
||||
if (isThisAreaSpoiled) {
|
||||
if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) {
|
||||
if (OTRGlobals::Instance->gRandoContext->GetDungeons()->GetDungeonFromScene(DungeonSceneLookupByArea(rcArea))->IsMQ())
|
||||
ImGui::Text("(%d/%d) - MQ", areaChecksGotten[rcArea], areaCheckTotals[rcArea]);
|
||||
else
|
||||
ImGui::Text("(%d/%d) - Vanilla", areaChecksGotten[rcArea], areaCheckTotals[rcArea]);
|
||||
} else {
|
||||
ImGui::Text("(%d/%d)", areaChecksGotten[rcArea], areaCheckTotals[rcArea]);
|
||||
std::ostringstream areaTotalsSS;
|
||||
std::ostringstream areaTotalsTooltipSS;
|
||||
|
||||
areaTotalsSS << "(";
|
||||
if (enableAvailableChecks) {
|
||||
areaTotalsSS << static_cast<uint16_t>(areaChecksAvailable[rcArea]) << " / ";
|
||||
areaTotalsTooltipSS << "Available / ";
|
||||
}
|
||||
areaTotalsSS << static_cast<uint16_t>(areaChecksGotten[rcArea]) << " / " << static_cast<uint16_t>(areaCheckTotals[rcArea]) << ")";
|
||||
areaTotalsTooltipSS << "Checked / Total";
|
||||
|
||||
if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) {
|
||||
if (OTRGlobals::Instance->gRandoContext->GetDungeons()->GetDungeonFromScene(DungeonSceneLookupByArea(rcArea))->IsMQ()) {
|
||||
areaTotalsSS << " - MQ";
|
||||
} else {
|
||||
areaTotalsSS << " - Vanilla";
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text(areaTotalsSS.str().c_str());
|
||||
UIWidgets::Tooltip(areaTotalsTooltipSS.str().c_str());
|
||||
} else {
|
||||
ImGui::Text("???");
|
||||
}
|
||||
|
@ -1561,6 +1641,12 @@ void DrawLocation(RandomizerCheck rc) {
|
|||
Rando::ItemLocation* itemLoc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
RandomizerCheckStatus status = itemLoc->GetCheckStatus();
|
||||
bool skipped = itemLoc->GetIsSkipped();
|
||||
bool available = itemLoc->IsAvailable();
|
||||
|
||||
if (enableAvailableChecks && onlyShowAvailable && !available) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == RCSHOW_COLLECTED) {
|
||||
if (!showHidden && hideCollected) {
|
||||
return;
|
||||
|
@ -1637,10 +1723,18 @@ void DrawLocation(RandomizerCheck rc) {
|
|||
OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(false);
|
||||
areaChecksGotten[loc->GetArea()]--;
|
||||
totalChecksGotten--;
|
||||
if (available) {
|
||||
areaChecksAvailable[loc->GetArea()]++;
|
||||
totalChecksAvailable++;
|
||||
}
|
||||
} else {
|
||||
OTRGlobals::Instance->gRandoContext->GetItemLocation(rc)->SetIsSkipped(true);
|
||||
areaChecksGotten[loc->GetArea()]++;
|
||||
totalChecksGotten++;
|
||||
if (available) {
|
||||
areaChecksAvailable[loc->GetArea()]--;
|
||||
totalChecksAvailable--;
|
||||
}
|
||||
}
|
||||
UpdateOrdering(loc->GetArea());
|
||||
UpdateInventoryChecks();
|
||||
|
@ -1654,7 +1748,19 @@ void DrawLocation(RandomizerCheck rc) {
|
|||
ImGui::SameLine();
|
||||
|
||||
//Draw
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(mainColor.r / 255.0f, mainColor.g / 255.0f, mainColor.b / 255.0f, mainColor.a / 255.0f));
|
||||
ImVec4 styleColor(mainColor.r / 255.0f, mainColor.g / 255.0f, mainColor.b / 255.0f, mainColor.a / 255.0f);
|
||||
if (enableAvailableChecks) {
|
||||
if (itemLoc->HasObtained()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0, 0, 0, 0));
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, styleColor);
|
||||
}
|
||||
ImGui::Text("%s", available ? ICON_FA_UNLOCK : ICON_FA_LOCK);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, styleColor);
|
||||
ImGui::Text("%s", txt.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
|
@ -1718,7 +1824,7 @@ void DrawLocation(RandomizerCheck rc) {
|
|||
if (conditionStr != "true") {
|
||||
UIWidgets::Tooltip(conditionStr.c_str());
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1796,6 +1902,55 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName,
|
|||
UIWidgets::PopStyleCombobox();
|
||||
}
|
||||
|
||||
void RecalculateAvailableChecks() {
|
||||
if (!enableAvailableChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS);
|
||||
StartPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS);
|
||||
|
||||
std::vector<RandomizerCheck> targetLocations;
|
||||
targetLocations.reserve(RR_MAX);
|
||||
for (auto& location : Rando::StaticData::GetLocationTable()) {
|
||||
RandomizerCheck rc = location.GetRandomizerCheck();
|
||||
Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
itemLocation->SetAvailable(false);
|
||||
if (!itemLocation->HasObtained()) {
|
||||
targetLocations.emplace_back(rc);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RandomizerCheck> availableChecks = ReachabilitySearch(targetLocations, RG_NONE, true);
|
||||
for (auto& rc : availableChecks) {
|
||||
const auto& location = Rando::StaticData::GetLocation(rc);
|
||||
const auto& itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
if (location->GetRCType() == RCTYPE_SHOP && itemLocation->GetCheckStatus() == RCSHOW_IDENTIFIED) {
|
||||
if (CanBuyAnother(rc)) {
|
||||
itemLocation->SetAvailable(true);
|
||||
}
|
||||
} else {
|
||||
itemLocation->SetAvailable(true);
|
||||
}
|
||||
}
|
||||
|
||||
totalChecksAvailable = 0;
|
||||
for (auto& [rcArea, vec] : checksByArea) {
|
||||
areaChecksAvailable[rcArea] = 0;
|
||||
for (auto& rc : vec) {
|
||||
Rando::ItemLocation* itemLocation = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc);
|
||||
if (itemLocation->IsAvailable() && IsVisibleInCheckTracker(rc) && !IsCheckHidden(rc)) {
|
||||
areaChecksAvailable[rcArea]++;
|
||||
}
|
||||
}
|
||||
totalChecksAvailable += areaChecksAvailable[rcArea];
|
||||
}
|
||||
|
||||
StopPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS);
|
||||
SPDLOG_INFO("Recalculate Available Checks Time: {}ms", GetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS).count());
|
||||
}
|
||||
|
||||
|
||||
void CheckTrackerWindow::Draw() {
|
||||
if (!IsVisible()) {
|
||||
return;
|
||||
|
@ -1850,7 +2005,7 @@ void CheckTrackerSettingsWindow::DrawElement() {
|
|||
.Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked.").Color(THEME_COLOR));
|
||||
if (UIWidgets::CVarCheckbox("Hide unshuffled shop item checks", CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"),
|
||||
UIWidgets::CheckboxOptions().Tooltip("If enabled, will prevent the tracker from displaying slots with non-shop-item shuffles.").Color(THEME_COLOR))) {
|
||||
hideShopUnshuffledChecks = !hideShopUnshuffledChecks;
|
||||
hideShopUnshuffledChecks = CVarGetInteger(CVAR_TRACKER_CHECK("HideUnshuffledShopChecks"), 0);
|
||||
UpdateFilters();
|
||||
}
|
||||
if (UIWidgets::CVarCheckbox("Always show gold skulltulas", CVAR_TRACKER_CHECK("AlwaysShowGSLocs"),
|
||||
|
@ -1860,6 +2015,11 @@ void CheckTrackerSettingsWindow::DrawElement() {
|
|||
}
|
||||
UIWidgets::CVarCheckbox("Show Logic", CVAR_TRACKER_CHECK("ShowLogic"),
|
||||
UIWidgets::CheckboxOptions().Tooltip("If enabled, will show a check's logic when hovering over it.").Color(THEME_COLOR));
|
||||
if (UIWidgets::CVarCheckbox("Enable Available Checks", CVAR_TRACKER_CHECK("EnableAvailableChecks"),
|
||||
UIWidgets::CheckboxOptions().Tooltip("If enabled, will show the checks that are available to be collected with your current progress.").Color(THEME_COLOR))) {
|
||||
enableAvailableChecks = CVarGetInteger(CVAR_TRACKER_CHECK("EnableAvailableChecks"), 0);
|
||||
RecalculateAvailableChecks();
|
||||
}
|
||||
|
||||
// Filtering settings
|
||||
UIWidgets::PaddedSeparator();
|
||||
|
|
|
@ -61,4 +61,5 @@ void UpdateAllOrdering();
|
|||
void UpdateAllAreas();
|
||||
void RecalculateAllAreaTotals();
|
||||
void SpoilAreaFromCheck(RandomizerCheck rc);
|
||||
void RecalculateAvailableChecks();
|
||||
} // namespace CheckTracker
|
||||
|
|
|
@ -306,11 +306,13 @@ extern "C" void Randomizer_InitSaveFile() {
|
|||
|
||||
// Malon/Talon back at ranch.
|
||||
Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG);
|
||||
Flags_SetRandomizerInf(RAND_INF_WEIRD_EGG);
|
||||
Flags_SetEventChkInf(EVENTCHKINF_TALON_WOKEN_IN_CASTLE);
|
||||
Flags_SetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE);
|
||||
|
||||
// Set "Got Zelda's Letter" flag. Also ensures Saria is back at SFM.
|
||||
Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_ZELDAS_LETTER);
|
||||
Flags_SetRandomizerInf(RAND_INF_ZELDAS_LETTER);
|
||||
Flags_SetRandomizerInf(RAND_INF_CHILD_TRADES_HAS_LETTER_ZELDA);
|
||||
|
||||
// Got item from Impa.
|
||||
|
@ -321,7 +323,6 @@ extern "C" void Randomizer_InitSaveFile() {
|
|||
// Set this at the end to ensure we always start with the letter.
|
||||
// This is for the off chance, we got the Weird Egg from Impa (which should never happen).
|
||||
INV_CONTENT(ITEM_LETTER_ZELDA) = ITEM_LETTER_ZELDA;
|
||||
Flags_SetRandomizerInf(RAND_INF_CHILD_TRADES_HAS_LETTER_ZELDA);
|
||||
}
|
||||
|
||||
if (Randomizer_GetSettingValue(RSK_SHUFFLE_MASTER_SWORD) && startingAge == RO_AGE_ADULT) {
|
||||
|
|
|
@ -459,10 +459,16 @@ void SaveManager::LoadRandomizerVersion3() {
|
|||
});
|
||||
|
||||
randoContext->GetTrials()->SkipAll();
|
||||
SaveManager::Instance->LoadArray("requiredTrials", randoContext->GetOption(RSK_TRIAL_COUNT).Get() + 1, [&](size_t i) {
|
||||
SaveManager::Instance->LoadArray("requiredTrials", randoContext->GetOption(RSK_TRIAL_COUNT).Get(), [&](size_t i) {
|
||||
size_t trialId;
|
||||
SaveManager::Instance->LoadData("", trialId);
|
||||
randoContext->GetTrial(trialId)->SetAsRequired();
|
||||
});
|
||||
|
||||
SaveManager::Instance->LoadArray("trickOptions", RT_MAX, [&](size_t i) {
|
||||
uint8_t value = 0;
|
||||
SaveManager::Instance->LoadData("", value);
|
||||
randoContext->GetTrickOption(RandomizerTrick(i)).Set(value);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -596,6 +602,10 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
|
|||
SaveManager::Instance->SaveData("", i);
|
||||
}
|
||||
});
|
||||
|
||||
SaveManager::Instance->SaveArray("trickOptions", RT_MAX, [&](size_t i) {
|
||||
SaveManager::Instance->SaveData("", randoContext->GetTrickOption(RandomizerTrick(i)).Get());
|
||||
});
|
||||
}
|
||||
|
||||
// Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any class initialization stuff here
|
||||
|
|
|
@ -1775,7 +1775,7 @@ void SohMenu::AddMenuEnhancements() {
|
|||
.WindowName("Additional Timers")
|
||||
.Options(WindowButtonOptions().Tooltip("Enables the separate Additional Timers Window."));
|
||||
AddWidget(path, "Font Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT)
|
||||
.CVar(CVAR_ENHANCEMENT("TimeDisplay.FontScale"))
|
||||
.CVar(CVAR_TIME_DISPLAY("FontScale"))
|
||||
.Callback([](WidgetInfo& info) {
|
||||
TimeDisplayInitSettings();
|
||||
})
|
||||
|
@ -1786,7 +1786,7 @@ void SohMenu::AddMenuEnhancements() {
|
|||
.Format("%.2fx")
|
||||
);
|
||||
AddWidget(path, "Hide Background", WIDGET_CVAR_CHECKBOX)
|
||||
.CVar(CVAR_ENHANCEMENT("TimeDisplay.ShowWindowBG"))
|
||||
.CVar(CVAR_TIME_DISPLAY("ShowWindowBG"))
|
||||
.Callback([](WidgetInfo& info) {
|
||||
TimeDisplayInitSettings();
|
||||
});
|
||||
|
|
|
@ -299,12 +299,8 @@ namespace SOH {
|
|||
{ MigrationAction::Rename, "gUniformLR", "gEnhancements.FixMenuLR" },
|
||||
{ MigrationAction::Rename, "gVisualAgony", "gEnhancements.VisualAgony" },
|
||||
{ MigrationAction::Rename, "gVoidDamageMul", "gEnhancements.VoidDamageMult" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.ShowAdditionalTimers", "gEnhancements.GameplayStats.ShowAdditionalTimers" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.ShowDebugInfo", "gEnhancements.GameplayStats.ShowDebugInfo" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.RoomBreakdown", "gEnhancements.GameplayStats.RoomBreakdown" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.ShowIngameTimer", "gEnhancements.GameplayStats.ShowInGameTimer" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.TimestampsReverse", "gEnhancements.GameplayStats.ReverseTimestamps" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.RTATiming", "gEnhancements.GameplayStats.RTATiming" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.ShowIngameTimer", "gGameplayStats.ShowInGameTimer" },
|
||||
{ MigrationAction::Rename, "gGameplayStats.TimestampsReverse", "gGameplayStats.ReverseTimestamps" },
|
||||
{ MigrationAction::Rename, "gMirroredWorld", "gEnhancements.MirroredWorld" },
|
||||
{ MigrationAction::Rename, "gBetaQuestWorld", "gCheats.BetaQuestWorld" },
|
||||
{ MigrationAction::Rename, "gBombTimerMultiplier", "gCheats.BombTimerMultiplier" },
|
||||
|
|
|
@ -14,4 +14,6 @@
|
|||
#define CVAR_GENERAL(var) CVAR_PREFIX_GENERAL "." var
|
||||
#define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var
|
||||
#define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE("CrowdControl." var)
|
||||
#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE("Sail." var)
|
||||
#define CVAR_REMOTE_SAIL(var) CVAR_REMOTE("Sail." var)
|
||||
#define CVAR_GAMEPLAY_STATS(var) CVAR_PREFIX_GAMEPLAY_STATS "." var
|
||||
#define CVAR_TIME_DISPLAY(var) CVAR_PREFIX_TIME_DISPLAY "." var
|
|
@ -6123,7 +6123,7 @@ void Interface_DrawTotalGameplayTimer(PlayState* play) {
|
|||
// Draw timer based on the Gameplay Stats total time.
|
||||
|
||||
if ((IS_BOSS_RUSH && gSaveContext.ship.quest.data.bossRush.options[BR_OPTIONS_TIMER] == BR_CHOICE_TIMER_YES) ||
|
||||
(CVarGetInteger(CVAR_ENHANCEMENT("GameplayStats.ShowIngameTimer"), 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2)) {
|
||||
(CVarGetInteger(CVAR_GAMEPLAY_STATS("ShowIngameTimer"), 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2)) {
|
||||
|
||||
s32 X_Margins_Timer = 0;
|
||||
if (CVarGetInteger(CVAR_COSMETIC("HUD.IGT.UseMargins"), 0) != 0) {
|
||||
|
|
|
@ -317,7 +317,8 @@ void KaleidoScope_HandleItemCycleExtras(PlayState* play, u8 slot, bool canCycle,
|
|||
|
||||
bool CanMaskSelect() {
|
||||
if (IS_RANDO) {
|
||||
return CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0) /* || Randomizer_GetSettingValue(RSK_SHUFFLE_CHILD_TRADE) */;
|
||||
return CVarGetInteger(CVAR_ENHANCEMENT("MaskSelect"), 0)
|
||||
&& Flags_GetRandomizerInf(RAND_INF_ZELDAS_LETTER);/* || Randomizer_GetSettingValue(RSK_SHUFFLE_CHILD_TRADE) */
|
||||
}
|
||||
|
||||
// only allow mask select when:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue