Compare commits

..

6 Commits

Author SHA1 Message Date
gymnast86 9756612b35 Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer 2026-07-02 20:01:09 -07:00
gymnast86 49f27acb2b fix rando config not saving when it should 2026-07-02 20:01:02 -07:00
gymnast86 d1ea637d22 add midna hair and charge ring colors 2026-07-02 20:00:47 -07:00
Irastris 42910ab2fd Restore simultaneous gyro and joystick input for first person aiming (#2164) 2026-07-02 00:13:22 -06:00
Olivia!! f54892b2c2 do not disable regular touch input if menu touch is disabled (#2152) 2026-07-02 00:03:53 -06:00
Luke Street f32e069c4b Improve mouse hiding logic (#2163)
* Improve mouse hiding logic

* Restore ImGui logic
2026-07-02 02:03:18 -04:00
59 changed files with 512 additions and 2969 deletions
+2 -5
View File
@@ -1466,7 +1466,6 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiMenuRandomizer.cpp
src/dusk/imgui/ImGuiMenuRandomizer.hpp
src/dusk/imgui/ImGuiArchipelagoDebug.cpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
@@ -1542,8 +1541,6 @@ set(DUSK_FILES
src/dusk/ui/rando_config.hpp
src/dusk/ui/rando_seed_generation.cpp
src/dusk/ui/rando_seed_generation.hpp
src/dusk/ui/archi_connect_modal.cpp
src/dusk/ui/archi_connect_modal.hpp
src/dusk/achievements.cpp
src/dusk/iso_validate.cpp
src/dusk/livesplit.cpp
@@ -1559,6 +1556,8 @@ set(DUSK_FILES
src/dusk/action_bindings.cpp
src/dusk/cosmetics/color_utils.hpp
src/dusk/cosmetics/color_utils.cpp
src/dusk/cosmetics/midna_hair_color.hpp
src/dusk/cosmetics/midna_hair_color.cpp
src/dusk/cosmetics/texture_utils.hpp
src/dusk/cosmetics/texture_utils.cpp
# Randomizer files
@@ -1649,8 +1648,6 @@ set(DUSK_FILES
src/dusk/randomizer/generator/utility/time.cpp
src/dusk/randomizer/generator/utility/time.hpp
src/dusk/randomizer/generator/utility/yaml.hpp
# Archipelago Files
src/dusk/archipelago/archipelago_context.cpp
)
set(DUSK_HTTP_BACKEND_FILES
+4
View File
@@ -3917,6 +3917,10 @@ public:
u16 getEventId() { return mMsgFlow.getEventId(); }
bool checkSwimming() {return checkModeFlg(MODE_SWIMMING);}
BE<short>** getDomeLockChromaTable() {
return &field_0x0724->mAnmCRegDataR;
}
#endif
static daAlink_BckData const m_mainBckShield[20];
-32
View File
@@ -1128,15 +1128,6 @@ void dComIfGs_setWarpItemData(char const* stage, cXyz pos, s16 angle, s8 roomNo,
u8 param_5);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageTbox(int i_stageNo, int i_no);
#if TARGET_PC
void dComIfGs_onStageTbox(int i_stageNo, int i_no);
void dComIfGs_offStageTbox(int i_stageNo, int i_no);
void dComIfGs_onStageItem(int i_stageNo, int i_no);
void dComIfGs_offStageItem(int i_stageNo, int i_no);
#endif
void dComIfGs_onStageSwitch(int i_stageNo, int i_no);
void dComIfGs_offStageSwitch(int i_stageNo, int i_no);
BOOL dComIfGs_isStageSwitch(int i_stageNo, int i_no);
@@ -1999,23 +1990,6 @@ inline void dComIfGs_onRegionFlag(int i_stageNo, int i_no) {
const int shift = i_no % 8;
regionFlags[offset] |= (0x80 >> shift);
}
inline void dComIfGs_onSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().onTbox(i_no);
}
inline void dComIfGs_offSaveTbox(int i_stageNo, int i_no) {
g_dComIfG_gameInfo.info.getSavedata().getSave(i_stageNo).getBit().offTbox(i_no);
}
inline void dComIfGs_onSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().onItem(i_no);
}
inline void dComIfGs_offSaveItem(int i_no) {
g_dComIfG_gameInfo.info.getMemory().getBit().offItem(i_no);
}
#endif
inline BOOL dComIfGs_isSaveTbox(int i_stageNo, int i_no) {
@@ -2482,12 +2456,6 @@ inline void dComIfGs_onItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.onItem(i_bitNo, i_roomNo);
}
#if TARGET_PC
inline void dComIfGs_offItem(int i_bitNo, int i_roomNo) {
g_dComIfG_gameInfo.info.offItem(i_bitNo, i_roomNo);
}
#endif
inline bool dComIfGs_isItem(int i_bitNo, int i_roomNo) {
return g_dComIfG_gameInfo.info.isItem(i_bitNo, i_roomNo);
}
-4
View File
@@ -205,7 +205,6 @@ public:
DATASELPROC_SELECT_DATA_NAME_MOVE,
#if TARGET_PC
DATASELPROC_SELECT_DATA_PLAY_MOVE, // Select between vanilla or randomizer play
DATASELPROC_MENU_ARCHIPELAGO_CONNECT, // Wait for archipelago to connect
#endif
DATASELPROC_SELECT_DATA_OPENERASE_MOVE,
DATASELPROC_MENU_SELECT,
@@ -337,7 +336,6 @@ public:
void selectDataNameMove();
#if TARGET_PC
void selectDataPlayTypeMove();
void menuArchipelagoConnect();
#endif
void selectDataOpenEraseMove();
void menuSelect();
@@ -744,8 +742,6 @@ public:
dDlst_FileSelFade_c mFadeDlst;
bool mStartNameAnm;
bool mBackToFileSelect;
bool mArchipelagoBeginConnect;
bool mArchiStartCloseFile;
int mPendingRmlCloseFrames{0};
} mDusk;
#endif
-1
View File
@@ -250,7 +250,6 @@ void item_func_FUSED_SHADOW_1();
void item_func_FUSED_SHADOW_2();
void item_func_FUSED_SHADOW_3();
void item_func_MIRROR_PIECE_1();
void item_func_ARCHIPELAGO_ITEM();
#endif
void item_func_POU_SPIRIT();
#if TARGET_PC
+1 -1
View File
@@ -638,7 +638,7 @@ enum {
/* 0xD9 */ dItemNo_Randomizer_FUSED_SHADOW_2_e,
/* 0xDA */ dItemNo_Randomizer_FUSED_SHADOW_3_e,
/* 0xDB */ dItemNo_Randomizer_MIRROR_PIECE_1_e,
/* 0xDC */ dItemNo_Randomizer_ARCHIPELAGO_ITEM_e,
/* 0xDC */ dItemNo_Randomizer_NOENTRY_220_e,
/* 0xDD */ dItemNo_Randomizer_NOENTRY_221_e,
/* 0xDE */ dItemNo_Randomizer_NOENTRY_222_e,
/* 0xDF */ dItemNo_Randomizer_NOENTRY_223_e,
-3
View File
@@ -993,9 +993,6 @@ public:
BOOL isSwitch(int i_no, int i_roomNo) const;
BOOL revSwitch(int i_no, int i_roomNo);
void onItem(int i_no, int i_roomNo);
#if TARGET_PC
void offItem(int i_no, int i_roomNo);
#endif
BOOL isItem(int i_no, int i_roomNo) const;
void onActor(int i_no, int i_roomNo);
void offActor(int i_no, int i_roomNo);
+4 -4
View File
@@ -4,9 +4,9 @@
namespace dusk::mouse {
void read();
void getAimDeltas(float& out_yaw, float& out_pitch);
void getCameraDeltas(float& out_yaw, float& out_pitch);
void get_aim_deltas(float& out_yaw, float& out_pitch);
void get_camera_deltas(float& out_yaw, float& out_pitch);
void handle_event(const SDL_Event& event) noexcept;
void onFocusLost();
void onFocusGained();
void on_focus_lost();
void on_focus_gained();
} // namespace dusk::mouse
+3 -7
View File
@@ -310,13 +310,6 @@ struct UserSettings {
struct {
std::array<ConfigVar<std::string>, 3> seedHashes;
} randomizer;
// Archipelago Settings
struct {
std::array<ConfigVar<std::string>, 3> savesServerIP;
std::array<ConfigVar<std::string>, 3> savesServerPass;
std::array<ConfigVar<std::string>, 3> savesSlotName;
} archipelago;
// Cosmetics
struct {
@@ -336,6 +329,9 @@ struct UserSettings {
ConfigVar<std::string> boomerangColor;
ConfigVar<std::string> ironBootsColor;
ConfigVar<std::string> spinnerColor;
ConfigVar<std::string> midnaHairBaseColor;
ConfigVar<std::string> midnaHairTipsColor;
ConfigVar<std::string> midnaChargeRingColor;
ConfigVar<std::string> linkHairColor;
ConfigVar<std::string> wolfLinkColor;
ConfigVar<std::string> eponaColor;
-15
View File
@@ -23,8 +23,6 @@
#include "d/actor/d_a_npc_tkc.h"
#include <cstring>
#include "dusk/archipelago/archipelago_context.hpp"
#if TARGET_PC
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
@@ -3304,15 +3302,6 @@ int daAlink_c::procCoDeadInit(int param_0) {
field_0x3080 = 0;
mProcVar5.field_0x3012 = 0x3F;
field_0x3198 = -1;
#if TARGET_PC
// trigger archipelago death link if applicable
if (dusk::archi::ArchipelagoContext::IsConnected()) {
dusk::archi::ArchipelagoContext::TryHandleDeathLink();
}
#endif
return 1;
}
@@ -4322,10 +4311,6 @@ int daAlink_c::procGanonFinishInit() {
dusk::m_speedrunInfo.stopRun();
}
}
if (dusk::archi::ArchipelagoContext::IsConnected()) {
dusk::archi::ArchipelagoContext::TryHandleGameComplete();
}
#endif
return 1;
+5 -12
View File
@@ -122,18 +122,11 @@ BOOL daAlink_c::setBodyAngleToCamera() {
var_f31 /= dComIfGp_getCameraZoomScale(field_0x317c);
}
#if TARGET_PC
if (dusk::getSettings().game.enableMouseAim && checkAimInputContext()) {
sp8 = mBodyAngle.x;
} else
#endif
{
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
shape_angle.y = shape_angle.y + (var_f31 * cM_ssin(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f)));
sp8 = mBodyAngle.x + (var_f31 * cM_scos(mStickAngle) IF_DUSK(* (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f)));
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
}
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
}
} else {
sp8 = mBodyAngle.x;
@@ -156,7 +149,7 @@ BOOL daAlink_c::setBodyAngleToCamera() {
f32 final_yaw = 0.f;
f32 final_pitch = 0.f;
if (dusk::getSettings().game.enableMouseAim) {
dusk::mouse::getAimDeltas(final_yaw, final_pitch);
dusk::mouse::get_aim_deltas(final_yaw, final_pitch);
}
if (dusk::getSettings().game.enableGyroAim) {
f32 gyro_yaw = 0.f;
+25
View File
@@ -6889,6 +6889,31 @@ void daAlink_c::setWolfLockDomeModel() {
} else {
field_0x0724->setFrame(0.0f);
}
#if TARGET_PC
// Replace charge ring color
const auto& domeRingColorStr = dusk::getSettings().cosmetics.midnaChargeRingColor.getValue();
if (dusk::cosmetics::is_valid_hex_color_str(domeRingColorStr)) {
auto domeRingColor = dusk::cosmetics::hex_color_str_to_gx_color(domeRingColorStr);
const u8 domeWave1RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
const u8 domeWave2RGBA[3] = {domeRingColor.r, domeRingColor.g, domeRingColor.b};
u8** chromaRegisterTable = reinterpret_cast<u8**>(daAlink_getAlinkActorClass()->getDomeLockChromaTable());
for (int i = 0; i < 3; i++)
{
u8* currentTable = chromaRegisterTable[i];
const u8 currentWave1Color = domeWave1RGBA[i];
const u8 currentWave2Color = domeWave2RGBA[i];
const u8 currentBaseColor = (currentWave1Color + currentWave2Color) / 2;
currentTable[0x3] = currentBaseColor; // Set Alpha for the ring base
currentTable[0x13] = currentWave1Color; // Set Alpha for ring wave 1
currentTable[0x23] = currentWave2Color; // Set Alpha for ring wave 2
currentTable[0xB] = currentBaseColor; // Set Alpha for darkworld ring base
currentTable[0x1B] = currentWave1Color; // Set Alpha for darkworld ring wave 1
currentTable[0x2B] = currentWave2Color; // Set Alpha for darkworld ring wave 2
}
}
#endif
}
void daAlink_c::setWolfBallModel() {
+35 -31
View File
@@ -14,7 +14,11 @@
#include "d/d_msg_object.h"
#include "d/d_s_play.h"
#include "d/d_debug_viewer.h"
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/cosmetics/midna_hair_color.hpp"
#endif
static f32 dummy_lit_3777(int idx, u8 foo) {
Vec dummy_vec = {0.0f, 0.0f, 0.0f};
@@ -686,28 +690,28 @@ cPhs_Step daMidna_c::create() {
mpMorf->modelCalc();
setBodyPartMatrix();
field_0x6e0.r = l_normalColor.r;
field_0x6e0.g = l_normalColor.g;
field_0x6e0.b = l_normalColor.b;
field_0x6e0.a = l_normalColor.a;
field_0x6e0.r = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalColor()->r , l_normalColor.r);
field_0x6e0.g = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalColor()->g , l_normalColor.g);
field_0x6e0.b = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalColor()->b , l_normalColor.b);
field_0x6e0.a = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalColor()->a , l_normalColor.a);
if (dKy_darkworld_check()) {
field_0x6e8.r = l_normalKColor.r;
field_0x6e8.g = l_normalKColor.g;
field_0x6e8.b = l_normalKColor.b;
field_0x6e8.a = l_normalKColor.a;
field_0x6ec.r = l_normalKColor2.r;
field_0x6ec.g = l_normalKColor2.g;
field_0x6ec.b = l_normalKColor2.b;
field_0x6ec.a = l_normalKColor2.a;
field_0x6e8.r = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor()->r, l_normalKColor.r);
field_0x6e8.g = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor()->g, l_normalKColor.g);
field_0x6e8.b = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor()->b, l_normalKColor.b);
field_0x6e8.a = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor()->a, l_normalKColor.a);
field_0x6ec.r = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2()->r, l_normalKColor2.r);
field_0x6ec.g = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2()->g, l_normalKColor2.g);
field_0x6ec.b = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2()->b, l_normalKColor2.b);
field_0x6ec.a = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2()->a, l_normalKColor2.a);
} else {
field_0x6e8.r = l_lNormalKColor.r;
field_0x6e8.g = l_lNormalKColor.g;
field_0x6e8.b = l_lNormalKColor.b;
field_0x6e8.a = l_lNormalKColor.a;
field_0x6ec.r = l_lNormalKColor2.r;
field_0x6ec.g = l_lNormalKColor2.g;
field_0x6ec.b = l_lNormalKColor2.b;
field_0x6ec.a = l_lNormalKColor2.a;
field_0x6e8.r = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor()->r, l_lNormalKColor.r);
field_0x6e8.g = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor()->g, l_lNormalKColor.g);
field_0x6e8.b = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor()->b, l_lNormalKColor.b);
field_0x6e8.a = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor()->a, l_lNormalKColor.a);
field_0x6ec.r = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor2()->r, l_lNormalKColor2.r);
field_0x6ec.g = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor2()->g, l_lNormalKColor2.g);
field_0x6ec.b = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor2()->b, l_lNormalKColor2.b);
field_0x6ec.a = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor2()->a, l_lNormalKColor2.a);
}
model = mpShadowModel;
@@ -1060,7 +1064,7 @@ void daMidna_c::setBodyPartMatrix() {
const GXColorS10* color;
const GXColor* kcolor1;
const GXColor* kcolor2;
if (bvar2) {
if (bvar8) {
modelData->getMaterialNodePointer(2)->getShape()->show();
@@ -1068,24 +1072,24 @@ void daMidna_c::setBodyPartMatrix() {
modelData->getMaterialNodePointer(1)->getShape()->show();
}
kcolor1 = &l_bigKColor;
kcolor1 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_bigKColor(), &l_bigKColor);
if (dKy_darkworld_check()) {
color = &l_bigColor;
kcolor2 = &l_normalKColor2;
color = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_bigColor(), &l_bigColor);
kcolor2 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2(), &l_normalKColor2);
} else {
color = &l_lBigColor;
kcolor2 = &l_lBigKColor2;
color = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lBigColor(), &l_lBigColor);
kcolor2 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lBigKColor2(), &l_lBigKColor2);
}
} else {
modelData->getMaterialNodePointer(0)->getShape()->show();
color = &l_normalColor;
color = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalColor(), &l_normalColor);
if (dKy_darkworld_check()) {
kcolor1 = &l_normalKColor;
kcolor2 = &l_normalKColor2;
kcolor1 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor(), &l_normalKColor);
kcolor2 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_normalKColor2(), &l_normalKColor2);
} else {
kcolor1 = &l_lNormalKColor;
kcolor2 = &l_lNormalKColor2;
kcolor1 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor(), &l_lNormalKColor);
kcolor2 = DUSK_IF_ELSE(dusk::cosmetics::get_midna_hair_lNormalKColor2(), &l_lNormalKColor2);
}
}
+1 -1
View File
@@ -7716,7 +7716,7 @@ bool dCamera_c::freeCamera() {
f32 yaw_rad = 0.0f;
f32 pitch_rad = 0.0f;
dusk::mouse::getCameraDeltas(yaw_rad, pitch_rad);
dusk::mouse::get_camera_deltas(yaw_rad, pitch_rad);
if (dusk::getSettings().game.enableMouseCamera && (yaw_rad != 0.0f || pitch_rad != 0.0f) &&
!dComIfGp_checkCameraAttentionStatus(dComIfGp_getPlayerCameraID(0), 0x8))
{
-40
View File
@@ -2100,41 +2100,6 @@ BOOL dComIfGs_isStageTbox(int i_stageNo, int i_no) {
}
}
#if TARGET_PC
void dComIfGs_onStageTbox(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_onTbox(i_no);
} else {
dComIfGs_onSaveTbox(i_stageNo, i_no);
}
}
void dComIfGs_offStageTbox(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_offTbox(i_no);
} else {
dComIfGs_offSaveTbox(i_stageNo, i_no);
}
}
void dComIfGs_onStageItem(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
dComIfGs_onItem(i_no, -1);
} else {
dComIfGs_onSaveItem(i_no);
}
}
void dComIfGs_offStageItem(int i_stageNo, int i_no) {
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
// Need to subtract 0x80 (MEMORY_ITEM constant in d_save.cpp) because the above function does it
dComIfGs_offItem(i_stageNo, i_no - 0x80);
} else {
dComIfGs_offSaveItem(i_no);
}
}
#endif
void dComIfGs_onStageSwitch(int i_stageNo, int i_no) {
#if TARGET_PC
// Avoid trying to get the save table if stag info is NULL
@@ -2149,12 +2114,7 @@ void dComIfGs_onStageSwitch(int i_stageNo, int i_no) {
}
void dComIfGs_offStageSwitch(int i_stageNo, int i_no) {
#if TARGET_PC
// Avoid trying to get the save table if stag info is NULL
if (dComIfGp_getStageStagInfo() && i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
#else
if (i_stageNo == dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo())) {
#endif
dComIfGs_offSwitch(i_no, -1);
}
+2 -5
View File
@@ -16,7 +16,6 @@
#include "dusk/string.hpp"
#include "dusk/version.hpp"
#include "dusk/archipelago/archipelago_context.hpp"
dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) {
mArchive = i_archive;
@@ -121,11 +120,9 @@ int dFile_info_c::setSaveData(dSv_save_c* i_savedata, BOOL i_validChksum, u8 i_d
// If this is a randomizer file
auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(i_dataNo).getValue();
if (!curFileSeedHash.empty()) {
bool isArchipelago = dusk::archi::ArchipelagoContext::IsSeedHashArchipelago(curFileSeedHash);
// Overwrite "Save time" text with "Randomizer" or "Archipelago"
// Overwrite "Save time" text with "Randomizer"
auto saveTimeText = (J2DTextBox*)mFileInfo.Scr->search(MULTI_CHAR('f_s_t_02'));
dusk::SafeStringCopy(saveTimeText->getStringPtr(), isArchipelago ? "Archipelago" : "Randomizer");
dusk::SafeStringCopy(saveTimeText->getStringPtr(), "Randomizer");
saveTimeText->setHBinding(J2DTextBoxHBinding::HBIND_LEFT);
// Overwrite the "Total play time" text with the seed hash
+21 -111
View File
@@ -23,9 +23,6 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#include "dusk/archipelago/archipelago_context.hpp"
#include "dusk/ui/archi_connect_modal.hpp"
#if TARGET_PC
#include "dusk/config.hpp"
#include "dusk/menu_pointer.h"
@@ -302,7 +299,6 @@ static DataSelProcFunc DataSelProc[] = {
&dFile_select_c::selectDataNameMove,
#if TARGET_PC
&dFile_select_c::selectDataPlayTypeMove,
&dFile_select_c::menuArchipelagoConnect,
#endif
&dFile_select_c::selectDataOpenEraseMove,
&dFile_select_c::menuSelect,
@@ -1034,75 +1030,29 @@ void dFile_select_c::dataSelectStart() {
ketteiTxtDispAnmInit(0);
#endif
#if TARGET_PC
headerTxtSet(msgTbl[mSelectNum], 1, 0);
makeRecInfo(mSelectNum);
// Load the randomizer seed if one is tied to this file
auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(mSelectNum).getValue();
// If this is a vanilla file, clear rando data structures
if (curFileSeedHash.empty()) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
// ensure archipelago is not connected either
if (dusk::archi::ArchipelagoContext::IsConnected())
dusk::archi::ArchipelagoContext::DisconnectFromServer();
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectNum], SelOpenEndFrameTbl[mSelectNum]);
menuMoveAnmInitSet(799, 809);
selectWakuAlpahAnmInit(mSelectNum, 0xff, 0, g_fsHIO.select_box_appear_frames);
}
else if (dusk::archi::ArchipelagoContext::IsSeedHashArchipelago(curFileSeedHash)) {
if (dusk::archi::ArchipelagoContext::IsConnected()) {
// if connected, we need to check if we're already connected to the right slot/seed,
// if so, we can continue straight to file start, otherwise a disconnect must happen first.
if (!dusk::archi::ArchipelagoContext::IsCurrentSeedHash(curFileSeedHash)) {
mDataSelProc = DATASELPROC_MENU_ARCHIPELAGO_CONNECT;
mDusk.mArchipelagoBeginConnect = true;
mDusk.mArchiStartCloseFile = false;
}else {
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectNum], SelOpenEndFrameTbl[mSelectNum]);
menuMoveAnmInitSet(799, 809);
selectWakuAlpahAnmInit(mSelectNum, 0xff, 0, g_fsHIO.select_box_appear_frames);
}
}else {
mDataSelProc = DATASELPROC_MENU_ARCHIPELAGO_CONNECT;
mDusk.mArchipelagoBeginConnect = true;
mDusk.mArchiStartCloseFile = false;
}
}else {
// Reset randomizer state if we're switching to a different file
if (curFileSeedHash != randomizer_GetContext().mHash || g_randomizerState.mFileNum != mSelectNum) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
randomizer_GetContext().LoadFromHash(curFileSeedHash);
}
// disconnect from archipelago if applicable
if (dusk::archi::ArchipelagoContext::IsConnected())
dusk::archi::ArchipelagoContext::DisconnectFromServer();
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectNum], SelOpenEndFrameTbl[mSelectNum]);
menuMoveAnmInitSet(799, 809);
selectWakuAlpahAnmInit(mSelectNum, 0xff, 0, g_fsHIO.select_box_appear_frames);
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
}
#else
headerTxtSet(msgTbl[mSelectNum], 1, 0);
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectNum], SelOpenEndFrameTbl[mSelectNum]);
menuMoveAnmInitSet(799, 809);
selectWakuAlpahAnmInit(mSelectNum, 0xff, 0, g_fsHIO.select_box_appear_frames);
makeRecInfo(mSelectNum);
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
#if TARGET_PC
// Load the randomizer seed if one is tied to this file
auto curFileSeedHash = dusk::getSettings().randomizer.seedHashes.at(mSelectNum).getValue();
// If this is a vanilla file, clear rando data structures
if (curFileSeedHash.empty()) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
}
// Reset randomizer state if we're switching to a different file
else if (curFileSeedHash != randomizer_GetContext().mHash || g_randomizerState.mFileNum != mSelectNum) {
g_randomizerState = RandomizerState();
randomizer_GetContext() = RandomizerContext();
randomizer_GetContext().LoadFromHash(curFileSeedHash);
}
#endif
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
}
modoruTxtDispAnmInit(1);
@@ -1395,6 +1345,10 @@ void dFile_select_c::selectDataNameMove() {
// Custom Proc to allow the player to select the play type and a randomizer seed
// Initially copied and expanded upon from dFile_select_c::selectDataNameMove
void dFile_select_c::selectDataPlayTypeMove() {
bool isHeaderTxtChange = headerTxtChangeAnm();
bool isFileRecScale = fileRecScaleAnm2();
bool isModoruTxtDisp = modoruTxtDispAnm();
// If we want to start bringing in the name input
if (mDusk.mStartNameAnm) {
// Only do so when no documents are visible
@@ -1422,8 +1376,8 @@ void dFile_select_c::selectDataPlayTypeMove() {
}
}
// If the file select elements have disappeared, setup the play type modal
else if (headerTxtChangeAnm() == true && fileRecScaleAnm2() == true &&
modoruTxtDispAnm() == true)
else if (isHeaderTxtChange == true && isFileRecScale == true &&
isModoruTxtDisp == true)
{
// Push our modal for selecting the play type
auto& playTypeModal = dusk::ui::push_document(std::make_unique<dusk::ui::Modal>(dusk::ui::Modal::Props{
@@ -1447,18 +1401,6 @@ void dFile_select_c::selectDataPlayTypeMove() {
modal.hide(true);
dusk::ui::push_document(std::make_unique<dusk::ui::FileSelectRandomizerWindow>(this));
}},
// If Archipelago is selected, open archipelago settings window
dusk::ui::ModalAction{
.label = "Archipelago",
.onPressed = [this](dusk::ui::Modal& modal) {
// pre-emptively set file num here for context to use
dComIfGs_setDataNum(mSelectNum);
mDusk.mBackToFileSelect = false;
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
modal.hide(true);
dusk::ui::BeginArchipelagoConnectionUI(true);
}},
},
// If we dismiss this modal, go back to file selection
.onDismiss = [this](dusk::ui::Modal& modal) {
@@ -1484,38 +1426,6 @@ void dFile_select_c::selectDataPlayTypeMove() {
playTypeModal.focus();
}
}
void dFile_select_c::menuArchipelagoConnect() {
if (mDusk.mArchipelagoBeginConnect) {
// set file num here for context to use
dComIfGs_setDataNum(mSelectNum);
dusk::ui::BeginArchipelagoConnectionUI();
mDusk.mArchipelagoBeginConnect = false;
mDusk.mPendingRmlCloseFrames = 6;
}else if (mDusk.mArchiStartCloseFile) {
bool isHeaderTxtChange = headerTxtChangeAnm();
bool isSelDataMove = selectDataMoveAnm();
bool isKetteiTxtDisp = ketteiTxtDispAnm();
if (isHeaderTxtChange && isSelDataMove && isKetteiTxtDisp) {
mDataSelProc = DATASELPROC_SELECT_DATA_OPEN_MOVE;
mDusk.mArchipelagoBeginConnect = false;
mDusk.mArchiStartCloseFile = false;
}
}else if (!dusk::ui::any_document_visible()) {
if (mDusk.mPendingRmlCloseFrames > 0) {
mDusk.mPendingRmlCloseFrames -= 1;
}
if (mDusk.mPendingRmlCloseFrames == 0) {
mDusk.mArchiStartCloseFile = true;
selectDataMoveAnmInitSet(SelOpenStartFrameTbl[mSelectNum], SelOpenEndFrameTbl[mSelectNum]);
menuMoveAnmInitSet(799, 809);
selectWakuAlpahAnmInit(mSelectNum, 0xff, 0, g_fsHIO.select_box_appear_frames);
}
}
}
#endif
void dFile_select_c::selectDataOpenEraseMove() {
+1 -9
View File
@@ -16,8 +16,6 @@
#endif
#include <cstring>
#include "dusk/archipelago/archipelago_context.hpp"
static void (*item_func_ptr[256])() = {
item_func_HEART,
item_func_GREEN_RUPEE,
@@ -499,7 +497,7 @@ static void (*item_func_ptr_randomizer[256])() = {
/* 0xD9 */ item_func_FUSED_SHADOW_2,
/* 0xDA */ item_func_FUSED_SHADOW_3,
/* 0xDB */ item_func_MIRROR_PIECE_1,
/* 0xDC */ item_func_ARCHIPELAGO_ITEM,
/* 0xDC */ item_func_noentry,
/* 0xDD */ item_func_noentry,
/* 0xDE */ item_func_noentry,
/* 0xDF */ item_func_noentry,
@@ -553,7 +551,6 @@ void execItemGet(u8 i_itemNo) {
if (randomizer_IsActive()) {
i_itemNo = verifyProgressiveItem(i_itemNo);
g_randomizerState.mUpdateTracker = true;
dusk::archi::ArchipelagoContext::SetNeedUpdateLocations(true);
}
#endif
@@ -2183,11 +2180,6 @@ void item_func_MIRROR_PIECE_1() {
dComIfGs_onCollectMirror(0);
}
}
void item_func_ARCHIPELAGO_ITEM() {
}
#endif
void item_func_POU_SPIRIT() {
-26
View File
@@ -2068,32 +2068,6 @@ void dSv_info_c::onItem(int i_no, int i_roomNo) {
}
}
#if TARGET_PC
void dSv_info_c::offItem(int i_no, int i_roomNo) {
JUT_ASSERT(4398, (0 <= i_no && i_no < (MEMORY_ITEM+ DAN_ITEM+ ZONE_ITEM+ ONEZONE_ITEM)) || i_no == -1 || i_no == 255);
if (i_no == -1 || i_no == 255) {
return;
}
if (i_no < MEMORY_ITEM) {
mDan.offItem(i_no);
} else if (i_no < (MEMORY_ITEM + DAN_ITEM)) {
mMemory.getBit().offItem(i_no - MEMORY_ITEM);
} else {
JUT_ASSERT(4414, 0 <= i_roomNo && i_roomNo < 64);
int zoneNo = dComIfGp_roomControl_getZoneNo(i_roomNo);
JUT_ASSERT(4416, 0 <= zoneNo && zoneNo < ZONE_MAX);
if (i_no < (MEMORY_ITEM + DAN_ITEM + ZONE_ITEM)) {
mZone[zoneNo].getBit().offItem(i_no - (MEMORY_ITEM + DAN_ITEM));
} else {
mZone[zoneNo].getBit().offOneItem(i_no - (MEMORY_ITEM + DAN_ITEM + ZONE_ITEM));
}
}
}
#endif
BOOL dSv_info_c::isItem(int i_no, int i_roomNo) const {
JUT_ASSERT(4488, (0 <= i_no && i_no < (MEMORY_ITEM+ DAN_ITEM+ ZONE_ITEM+ ONEZONE_ITEM)) || i_no == -1 || i_no == 255);
@@ -1,965 +0,0 @@
#include <dusk/archipelago/archipelago_context.hpp>
#include <thread>
#include "Archipelago.h"
#include "d/d_item.h"
#include "d/actor/d_a_alink.h"
#include "dusk/config.hpp"
#include "dusk/logging.h"
#include "dusk/randomizer/game/tools.h"
#include "dusk/randomizer/game/verify_item_functions.h"
#include "dusk/randomizer/generator/logic/hints.hpp"
#include "dusk/ui/rando_config.hpp"
#include "dusk/ui/ui.hpp"
namespace dusk::archi
{
static constexpr int ARCHI_ITEM_OFFSET = 2320000;
struct SettingsNameConvert {
static constexpr std::string kDefaultYes = "On";
static constexpr std::string kDefaultNo = "Off";
std::string apName;
std::string dusklightName;
std::vector<std::pair<std::string, std::string>> optionsConvert;
const std::string& tryGetOptionConvert(const std::string& option) const {
if (optionsConvert.empty()) {
if (option == "Yes")
return kDefaultYes;
if (option == "No")
return kDefaultNo;
return option;
}
for (const auto& value : optionsConvert) {
if (value.first == option) {
return value.second;
}
}
return option;
}
};
static auto sArchiSettingToDusklight = std::to_array<SettingsNameConvert>({
{"", ""},
{"Golden Bugs Shuffled", "Golden Bugs"},
{"Sky Chracters Shuffled", "Sky Characters"},
{"NPC Items Shuffled", "Gifts From NPCs"},
{"Shop Items Shuffled", "Shop Items"},
{"Hidden Skills Shuffled", "Hidden Skills"},
{"Skip Prologue", "Skip Prologue"},
{"Faron Twilight Cleared", "Faron Twilight Cleared"},
{"Eldin Twilight Cleared", "Eldin Twilight Cleared"},
{"Lanayru Twilight Cleared", "Lanayru Twilight Cleared"},
{"Skip MDH", "Skip Midna's Desparate Hour"},
{"Open Map", "Unlock Map Regions"},
{"Increase Wallet", "Logic Increase Wallet Capacity"},
{"Transform Anywhere", "Logic Transform Anywhere"},
{"Bonks do Damage", "Bonks Do Damage"},
{"Lakebed Entrance Requirements", "Lakebed Does Not Require Water Bombs"},
{"Arbiters Grounds Entrance Requirements", "Arbiters Does Not Require Bulblin Camp"},
{"Snowpeak Entrance Requirements", "Snowpeak Does Not Require Reekfish Scent"},
{"City in the Sky Entrance Requirements", "City Does Not Require Filled Skybook"},
{"Goron Mines Entrance Requirements", "Goron Mines Entrance"},
{"Palace of Twilight Requirements", "Palace of Twilight Requirements"},
{"Faron Woods Logic", "Faron Woods Logic"},
{"Starting ToD", "Starting Time of Day"},
{"Skip Major Cutscenes", "Skip Major Cutscenes"},
{"Skip Minor Cutscenes", "Skip Minor Cutscenes"},
{"Open Door of Time", "Open Door of Time"},
{"Dungeon Rewards Progression", "Dungeon Rewards Can Be Anywhere", {
// these two are functionally identical in terms of tracker logic, so treat it as such
{"Anything", "On"},
{"Any Progressive", "On"},
{"Vanilla", "Off"},
}},
{"Small Key Settings", "Small Keys", {
{"Startwith", "Keysy"},
}},
{"Big Key Settings", "Big Keys", {
{"Startwith", "Keysy"},
}},
{"Map and Compass Settings", "Maps and Compasses", {
{"Startwith", "Start With"},
}},
{"Trap Frequency", "Trap Item Frequency", {
{"No Traps", "None"},
}},
{"Damage Magnification", "Logic Damage Multiplier", {
{"Ohko", "OHKO"},
}},
{"Logic Settings", "Logic Rules", {
{"Glitchless", "All Locations Reachable"},
{"Glitched", "Beatable Only"},
}},
{"Poes Shuffled", "Poe Souls", {
{"Yes", "All"},
{"No", "Vanilla"}
}}
});
ArchipelagoContext& instance() {
static ArchipelagoContext instance;
return instance;
}
const SettingsNameConvert& GetAPSettingNameConvert(const std::string& apSettingName) {
for (const auto& entry : sArchiSettingToDusklight) {
if (entry.apName == apSettingName)
return entry;
}
return sArchiSettingToDusklight[0];
}
const char* getMessageTypeName(AP_MessageType type) {
switch (type) {
case AP_MessageType::Plaintext:
return "Plaintext";
case AP_MessageType::ItemSend:
return "ItemSend";
case AP_MessageType::ItemRecv:
return "ItemRecv";
case AP_MessageType::Hint:
return "Hint";
case AP_MessageType::Countdown:
return "Countdown";
default:
return nullptr;
}
}
void ParseMessageData() {
auto msg = AP_GetLatestMessage();
switch (msg->type) {
case AP_MessageType::ItemSend: {
auto sendMsg = (AP_ItemSendMessage*)msg;
ui::push_toast({
.title = "Item Sent",
.content = fmt::format("Sent {} to {}", sendMsg->item, sendMsg->recvPlayer),
.duration = std::chrono::seconds(3),
});
DuskLog.info("[{}] {}", getMessageTypeName(msg->type), msg->text);
break;
}
case AP_MessageType::ItemRecv: {
auto recvMsg = (AP_ItemRecvMessage*)msg;
ui::push_toast({
.title = "Item Received",
.content = fmt::format("Got {} From {}", recvMsg->item, recvMsg->sendPlayer),
.duration = std::chrono::seconds(3),
});
// fallthrough for debug logging text contents
}
case AP_MessageType::Plaintext:
case AP_MessageType::Hint:
case AP_MessageType::Countdown:
DuskLog.info("[{}] {}", getMessageTypeName(msg->type), msg->text);
break;
default:
DuskLog.warn("Unknown message type! Type: {}", fmt::underlying(msg->type));
break;
}
AP_ClearLatestMessage();
}
void ArchipelagoContext::LoadTempItemInfo() {
auto itemDataTree = LOAD_EMBED_YAML(RANDO_DATA_PATH "items.yaml");
for (const auto& itemNode : itemDataTree) {
if (!itemNode["APItemId"]) {
DuskLog.warn("Item {} missing APItemId field!", itemNode["Name"].as<std::string>());
continue;
}
auto apItemId = itemNode["APItemId"].as<int>();
if (apItemId == -1)
continue;
auto id = itemNode["Id"].as<int>();
auto importance = randomizer::logic::item::ImportanceFromStr(itemNode["Importance"].as<std::string>());
auto itemName = itemNode["Name"].as<std::string>();
m_apItemToGameItem[apItemId] = {
id,
importance,
itemName
};
}
// add temporary replacement IDs for items not included in the base rando
m_apItemToGameItem[16] = { // Water Bombs (3)
0x16,
randomizer::logic::item::Importance::JUNK,
"Water Bombs 5"
};
m_apItemToGameItem[20] = { // Bomblings (3)
0x1A,
randomizer::logic::item::Importance::JUNK,
"Bomblings 5"
};
}
void ArchipelagoContext::LoadTempLocationInfo() {
auto locDataTree = LOAD_EMBED_YAML(RANDO_DATA_PATH "locations.yaml");
for (const auto& locNode : locDataTree) {
const auto& metadata = locNode["Metadata"];
auto locationName = locNode["Name"].as<std::string>();
if (!metadata.IsMap()) {
DuskLog.warn("Location {} missing correct Metadata field!", locationName);
continue;
}
if (!metadata["APLocationId"]) {
DuskLog.warn("Location {} missing APLocationId field!", locationName);
continue;
}
auto apLocationId = metadata["APLocationId"].as<int>();
if (apLocationId == -1)
continue;
m_apLocToGameLoc.push_back({
apLocationId,
locationName
});
}
}
void ArchipelagoContext::itemRecvImpl(int id, bool notify) {
if (!m_apItemToGameItem.contains(id)) {
DuskLog.warn("[AP] Got an invalid Item Id: {}", id);
return;
}
m_isAllowUpdateLocations = true; // guards against triggering UpdateCheckedLocations
auto& item = m_apItemToGameItem[id];
if (notify && item.importance == randomizer::logic::item::Importance::MAJOR) {
DuskLog.info("[AP] Adding Item: {}", item.itemName);
g_randomizerState.addItemToEventQueue(verifyProgressiveItem(item.itemId));
}else {
DuskLog.info("[AP] Silently Adding Item: {}", item.itemName);
execItemGet(item.itemId);
}
m_isAllowUpdateLocations = false;
}
int ArchipelagoContext::getItemIdFromApId(int apId) {
if (!m_apItemToGameItem.contains(apId)) {
DuskLog.warn("Got an invalid Item Id: {}", apId);
return -1;
}
auto& item = m_apItemToGameItem[apId];
return item.itemId;
}
std::string ArchipelagoContext::getLocationNameFromApId(int apId) const {
for (const auto& entry : m_apLocToGameLoc) {
if (entry.apId == apId)
return entry.locName;
}
return "";
}
bool ArchipelagoContext::tryKillPlayer() {
if (!m_isNeedPlayerDeath)
return false;
auto linkActor = daAlink_getAlinkActorClass();
if (!linkActor)
return false;
switch (linkActor->mProcID) {
case daAlink_c::PROC_WAIT:
case daAlink_c::PROC_TIRED_WAIT:
case daAlink_c::PROC_MOVE:
case daAlink_c::PROC_WOLF_WAIT:
case daAlink_c::PROC_WOLF_TIRED_WAIT:
case daAlink_c::PROC_WOLF_MOVE:
case daAlink_c::PROC_ATN_MOVE:
case daAlink_c::PROC_WOLF_ATN_AC_MOVE: {
// Check if link is currently in a cutscene
if (linkActor->checkEventRun())
break;
// Ensure that link is not currently in a message-based event.
if (linkActor->getEventId() != 0)
break;
dComIfGs_setLife(0);
m_isNeedPlayerDeath = false;
return true;
}
default:
break;
}
return false;
}
ArchipelagoContext::ArchipelagoContext() = default;
void ArchipelagoContext::SetServerIp(const std::string_view& ip, int file) {
getSettings().archipelago.savesServerIP[file].setValue(std::string(ip));
}
void ArchipelagoContext::SetSlotName(const std::string_view& name, int file) {
getSettings().archipelago.savesSlotName[file].setValue(std::string(name));
}
void ArchipelagoContext::SetPassword(const std::string_view& pass, int file) {
getSettings().archipelago.savesServerPass[file].setValue(std::string(pass));
}
const std::string& ArchipelagoContext::GetServerIp(int file) {
return getSettings().archipelago.savesServerIP[file].getValue();
}
const std::string& ArchipelagoContext::GetSlotName(int file) {
return getSettings().archipelago.savesSlotName[file].getValue();
}
const std::string& ArchipelagoContext::GetPassword(int file) {
return getSettings().archipelago.savesServerPass[file].getValue();
}
std::string ArchipelagoContext::GetArchipelagoSeedName() {
if (IsConnected()) {
auto& roomInfo = instance().m_roomInfo;
if (roomInfo.seed_name.empty()) {
DuskLog.warn("Got an invalid Seed Name!");
}
return fmt::format("AP_{}", roomInfo.seed_name);
}else {
DuskLog.fatal("Archipelago was not connected when attempting to get seed name!");
}
}
void ArchipelagoContext::GetSeedDirectoryPath(std::filesystem::path& outPath) {
if (IsConnected()) {
auto& roomInfo = instance().m_roomInfo;
outPath = ui::GetRandomizerPath() / "archipelago" / GetArchipelagoSeedName();
}
}
bool ArchipelagoContext::IsSeedHashArchipelago(const std::string& seedStr) {
return seedStr.starts_with("AP_");
}
bool ArchipelagoContext::IsCurrentSeedHash(const std::string& seedStr) {
return GetArchipelagoSeedName() == seedStr;
}
bool ArchipelagoContext::ConnectToServer(int file, bool isBlocking) {
config::Save();
instance().LoadTempItemInfo();
instance().LoadTempLocationInfo();
AP_SetLogCallback([](const std::string& msg) {
DuskLog.info("{}", msg);
});
AP_Init(GetServerIp(file).c_str(), "Twilight Princess", GetSlotName(file).c_str(), GetPassword(file).c_str());
AP_NetworkVersion ver{0, 6,7};
AP_SetClientVersion(&ver);
AP_SetDeathLinkSupported(true);
AP_SetDeathLinkRecvCallback([](std::string source, std::string cause) {
DuskLog.info("Player {} sent death link. Cause: {}", source, cause);
RequestPlayerDeath(true);
});
AP_SetItemClearCallback([]() {
DuskLog.info("Item Clear Callback Called!");
instance().m_isNeedResetInv = true;
});
AP_SetItemRecvCallback([](AP_NetworkItem& item, bool notify) {
DuskLog.debug("Item Receive Callback Called! Item: {} Notify: {}", item.item, notify);
HandleItemReceived(item, notify);
});
AP_SetLocationCheckedCallback([](int loc) {
DuskLog.info("Location Checked Callback Called! Location: {}", loc);
SetLocationChecked(loc, true);
});
AP_SetLocationInfoCallback([](std::vector<AP_NetworkItem> items) {
DuskLog.info("Got {} Location Scouts from Server.", items.size());
HandleReceiveLocationScout(items);
});
AP_RegisterSlotDataRawCallback("Settings", [](std::string data) {
DuskLog.info("Got Settings from Slot Data.");
instance().m_SettingsFile = data;
});
AP_RegisterSlotDataRawCallback("World Version", [](std::string data) {
DuskLog.info("TP APWorld Version: {}", data);
});
AP_Start();
// above func spawns a websocket thread, but there isn't really a good way to ensure a connection
// attempt has been made except to wait for that thread to tick once
if (isBlocking) {
// wait for ws thread to run a frame before checking for status
std::this_thread::sleep_for(std::chrono::seconds(1));
while (AP_GetConnectionStatus() == AP_ConnectionStatus::Connecting)
std::this_thread::yield();
if (!IsConnected()) {
DuskLog.error("Failed to connect to Archipelago Server!");
return false;
}
}
std::thread messageThread = std::thread(MessageThreadFunc);
messageThread.detach();
return true;
}
void ArchipelagoContext::DisconnectFromServer() {
AP_Shutdown();
}
bool ArchipelagoContext::IsConnected() {
auto status = AP_GetConnectionStatus();
return status == AP_ConnectionStatus::Connected || status == AP_ConnectionStatus::Authenticated;
}
void ArchipelagoContext::MessageThreadFunc() {
DuskLog.info("AP Thread started.");
if (IsConnected()) {
AP_GetRoomInfo(&instance().m_roomInfo);
instance().m_isEnableDeathLink = AP_IsDeathLinkEnabled();
RequestAllLocationScout();
}
while (IsConnected()) {
if (AP_IsMessagePending())
ParseMessageData();
}
DuskLog.info("AP Thread ended.");
}
void ArchipelagoContext::Execute() {
if (!IsConnected())
return;
// reset player inventory if server requested it
if (instance().m_isNeedResetInv) {
HandleResetInventory();
instance().m_isNeedResetInv = false;
return; // end execution early so next frame can re-add inventory if needed
}
// process death links
if (instance().tryKillPlayer()) {
// if successful, don't bother processing item queue or location checks
return;
}
// drain pending item queue here
instance().m_queueMutex.lock();
if (!instance().m_receivedItemsQueue.empty()) {
for (auto item : instance().m_receivedItemsQueue) {
instance().itemRecvImpl(item.first, item.second);
}
instance().m_receivedItemsQueue.clear();
}
instance().m_queueMutex.unlock();
// update location checks here if we need to
if (instance().m_isUpdateLocations) {
UpdateCheckedLocations();
instance().m_isUpdateLocations = false;
}
}
void ArchipelagoContext::HandleItemReceived(AP_NetworkItem& netItem, bool notify) {
int relativeId = netItem.item - ARCHI_ITEM_OFFSET;
// TODO: modify this to also include junk items like ammo
if (!notify && ((relativeId >= 0 && relativeId <= 6) || relativeId == 7)) {
// skip rupee refills so players cant abuse disconnect/reconnect
return;
}
if (!instance().m_isNeedResetInv && netItem.location != -1 && IsLocationChecked(netItem.location)) {
// no need to handle item if its location has already been checked
return;
}
instance().m_queueMutex.lock();
instance().m_receivedItemsQueue.push_back({relativeId, notify});
instance().m_queueMutex.unlock();
}
void ArchipelagoContext::HandleResetInventory() {
DuskLog.info("Resetting Inventory.");
// NOTE: this does not clear ALL things from save, so if a player managed to do something while disconnected from the archi, it might mess with things
auto& playerInfo = g_dComIfG_gameInfo.info.getPlayer();
// reset items
playerInfo.getItem().init();
playerInfo.getGetItem().init();
// reset collect (poes, shards, swords)
playerInfo.getCollect().init();
playerInfo.getPlayerStatusA().setMaxLife(15);
playerInfo.getPlayerStatusA().setWalletSize(WALLET);
// dont reset rupees, and instead reject rupee updates while refilling inv
// add back default items
execItemGet(dItemNo_WEAR_KOKIRI_e);
// sync all location collect flags with current collection status obtained from initial room connection
UpdateAllLocationState();
// clear all item-related flags
dComIfGs_offEventBit(0x2580); // Power up dominion rod
// shadow crystal
dComIfGs_offEventBit(0xD04); // Can transform at will
dComIfGs_offEventBit(0x501); // Midna Charge Unlocked
// hidden skills
dComIfGs_offEventBit(0x2904); // ENDING BLOW
dComIfGs_offEventBit(0x2908); // SHIELD ATTACK
dComIfGs_offEventBit(0x2902); // BACK SLICE
dComIfGs_offEventBit(0x2901); // HELM SPLITTER
dComIfGs_offEventBit(0x2A80); // MORTAL DRAW
dComIfGs_offEventBit(0x2A40); // JUMP STRIKE
dComIfGs_offEventBit(0x2A20); // GREAT SPIN
}
void ArchipelagoContext::HandleReceiveLocationScout(const std::vector<AP_NetworkItem>& items) {
for (const auto& item : items) {
int parsedItemId;
std::string parsedItemName;
if (item.player == AP_GetPlayerID()) {
int adjustedId = item.item - ARCHI_ITEM_OFFSET;
if (instance().m_apItemToGameItem.contains(adjustedId)) {
auto& itemInfo = instance().m_apItemToGameItem[adjustedId];
parsedItemId = itemInfo.itemId;
parsedItemName = itemInfo.itemName;
}else {
parsedItemId = -1;
parsedItemName = "Unknown";
}
}else {
parsedItemId = dItemNo_Randomizer_ARCHIPELAGO_ITEM_e;
parsedItemName = "Archipelago Item";
}
int locationId = item.location - ARCHI_ITEM_OFFSET;
auto locName = instance().getLocationNameFromApId(locationId);
if (locName.empty()) {
DuskLog.info("No location with ID {} found.", locationId);
continue;
}
bool collected = false;
if (instance().m_initLocationCollectState.contains(item.location))
collected = instance().m_initLocationCollectState[item.location];
instance().m_locationItemInfo[locName] = {
parsedItemId,
parsedItemName,
locName,
item.location,
collected
};
}
}
// TODO: atm this is a sort of lazy solution to not having direct access to what location was checked when an execItemGet is called
// so eventually finding a way to properly associate locations with their respective item get funcs would benefit this system
void ArchipelagoContext::UpdateCheckedLocations() {
auto& world = instance().m_archiWorld;
bool changed = false;
for (auto location : world->GetAllLocations()) {
// skip locations that aren't progression, which are locations that just aren't randomized
if (!location->IsProgression()) {
continue;
}
auto locName = location->GetName();
if (!instance().m_locationItemInfo.contains(locName)) {
DuskLog.debug("No item found for ({}).", locName);
continue;
}
auto& cachedLocData = instance().m_locationItemInfo[locName];
bool isCollected = isLocationObtained(location);
if (isCollected && !cachedLocData.collected) {
cachedLocData.collected = true;
AP_SendItem(cachedLocData.apLocationId);
changed = true;
}
}
if (!changed) {
DuskLog.warn("No locations had any changes! this might not be normal.");
}
}
void ArchipelagoContext::SetNeedUpdateLocations(bool update) {
if (!instance().m_isAllowUpdateLocations)
instance().m_isUpdateLocations = update;
}
bool ArchipelagoContext::IsLocationChecked(int locId) {
auto& world = instance().m_archiWorld;
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
if (locInfo.collected)
return true;
if (auto location = world->GetLocation(locInfo.locationName, true)) {
return isLocationObtained(location);
}
DuskLog.error("Failed to obtain location: {}", locName);
return false;
}
}
return false;
}
void ArchipelagoContext::SetLocationChecked(int locId, bool collected) {
// func was ran before location scouts could be sent out, cache result until scouts return.
if (!IsReceivedLocationScouts()) {
instance().m_initLocationCollectState[locId] = collected;
return;
}
auto& world = instance().m_archiWorld;
for (auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
locInfo.collected = collected;
// update location flags if possible
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
return;
setLocationCollected(location, collected);
return;
}
}
DuskLog.warn("No location found for locId {}.", locId);
}
void ArchipelagoContext::UpdateLocationState(int locId, bool collected) {
auto& world = instance().m_archiWorld;
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
continue;
setLocationCollected(location, collected);
return;
}
}
DuskLog.warn("No location found for locId {}.", locId);
}
void ArchipelagoContext::UpdateAllLocationState() {
auto& world = instance().m_archiWorld;
// TODO: find out why some locations seem to keep their collection state upon reset (bugs)
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
auto location = world->GetLocation(locInfo.locationName, true);
if (!location || !location->IsProgression())
continue;
setLocationCollected(location, locInfo.collected);
}
}
bool ArchipelagoContext::IsReceivedLocationScouts() {
return !instance().m_locationItemInfo.empty();
}
void ArchipelagoContext::TryHandleDeathLink() {
if (instance().m_isEnableDeathLink && !instance().m_isFromDeathLink) {
// TODO: come up with better death messages
AP_DeathLinkSend("%YOU% was unable to become the Hero of Twilight.");
}
}
bool ArchipelagoContext::TryHandleGameComplete() {
// TODO: maybe add support for other game completion types?
AP_StoryComplete();
return true;
}
void ArchipelagoContext::RequestAllLocationScout(bool isHint) {
std::set<int64_t> locations;
// TEMP: apworld has 475 locations with ids in sequential order, so add them all individually to location set
// (eventually we will iterate through locations.yaml for a better data-driven solution)
for (int i = 0; i < 475; i++) {
locations.insert(ARCHI_ITEM_OFFSET + i);
}
AP_SendLocationScouts(locations, isHint);
}
void ArchipelagoContext::RequestPlayerDeath(bool isDeathLink) {
instance().m_isNeedPlayerDeath = true;
instance().m_isFromDeathLink = isDeathLink;
}
bool ArchipelagoContext::GenerateConfigFromAP(randomizer::seedgen::config::Config& config, const std::string& settingsStr) {
YAML::Node apConfigYaml;
try {
apConfigYaml = YAML::Load(settingsStr);
}catch (YAML::BadFile& e) {
DuskLog.warn("Failed to load AP Config YAML file!");
return false;
}
config.SetSeed("Archipelago");
randomizer::seedgen::settings::Settings& settings = config.GetSettings();
// update settings using ap config
for (const auto& apSettingEntry : apConfigYaml) {
auto apSettingName = apSettingEntry.first.as<std::string>();
auto apSettingValue = apSettingEntry.second.as<std::string>();
const auto& settingConvert = GetAPSettingNameConvert(apSettingName);
if (!settingConvert.apName.empty()) {
auto& setting = settings.GetMap().at(settingConvert.dusklightName);
setting.SetCurrentOption(settingConvert.tryGetOptionConvert(apSettingValue));
} else if (apSettingName == "Castle Requirements") {
auto& setting = settings.GetMap().at("Hyrule Barrier Requirements");
// ap assumes max mirror shards/fused shadows/dungeons, so update those settings as well
if(apSettingValue == "Open")
setting.SetCurrentOption("Open");
else if(apSettingValue == "Vanilla")
setting.SetCurrentOption("Vanilla");
else if(apSettingValue == "Fused Shadows") {
setting.SetCurrentOption("Fused Shadows");
settings.GetMap().at("Hyrule Barrier Fused Shadows").SetCurrentOption("3");
}else if(apSettingValue == "Mirror Shards") {
setting.SetCurrentOption("Mirror Shards");
settings.GetMap().at("Hyrule Barrier Mirror Shards").SetCurrentOption("4");
}else if(apSettingValue == "All Dungeons") {
setting.SetCurrentOption("Dungeons");
settings.GetMap().at("Hyrule Barrier Dungeons").SetCurrentOption("8");
}
}else if (apSettingName == "Temple of Time Entrance Requirements") {
auto& setting = settings.GetMap().at("Sacred Grove Does Not Require Skull Kid");
auto& setting2 = settings.GetMap().at("Temple of Time Sword Requirement");
if(apSettingValue == "Closed") {
setting.SetCurrentOption("Off");
setting2.SetCurrentOption("Master Sword");
}else if (apSettingValue == "Open Grove") {
setting.SetCurrentOption("On");
setting2.SetCurrentOption("Master Sword");
}else if (apSettingValue == "Open") {
setting.SetCurrentOption("On");
setting2.SetCurrentOption("None");
}
}else {
DuskLog.debug("Missing Setting: {} Value: {}", apSettingName, apSettingValue);
}
}
return true;
}
int ArchipelagoContext::GetItemAtLocation(const std::string& locName) {
if (!instance().m_locationItemInfo.contains(locName)) {
DuskLog.warn("No item found for ({}).", locName);
return 0;
}
return instance().m_locationItemInfo[locName].itemId;
}
int ArchipelagoContext::GetItemAtLocation(int locId) {
for (const auto& [locName, locInfo] : instance().m_locationItemInfo) {
if (locInfo.apLocationId == locId) {
return locInfo.itemId;
}
}
return 0;
}
void ArchipelagoContext::CreateArchipelagoWorld() {
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
auto trackerRando = randomizer::Randomizer(workingDir);
trackerRando.GenerateTrackerWorld(false);
instance().m_archiWorld = std::move(trackerRando.GetWorlds().front());
}
void ArchipelagoContext::FillArchipelagoWorld() {
auto& world = instance().m_archiWorld;
if (world == nullptr) {
DuskLog.error("Archipelago world was not created!");
return;
}
auto& locationInfo = instance().m_locationItemInfo;
// fill all locations with data pulled from archi session
for (auto location : world->GetAllLocations()) {
// skip locations that aren't progression, which are locations that just aren't randomized
if (!location->IsProgression()) {
location->SetCurrentItem(location->GetOriginalItem());
continue;
}
auto locName = location->GetName();
if (!locationInfo.contains(locName)) {
if (!location->HasCategories("Warp Portal") &&
!location->HasCategories("Placeholder") &&
!location->HasCategories("Hint Sign"))
DuskLog.warn("Missing archipelago location data for: {}", locName);
auto origItem = location->GetOriginalItem();
// set location to original item
if (origItem->GetID() != -1) // ensure item is not nothing
location->SetCurrentItem(origItem);
else
DuskLog.info("Location ({}) does not have an original item!", locName);
continue;
}
auto& locInfo = locationInfo[locName];
if (locInfo.itemId != -1) {
location->SetCurrentItem(world->GetItem(locInfo.itemId));
}else {
DuskLog.info("Skipping location ({}) as item is -1.", locName);
}
}
}
void ArchipelagoContext::CreateRandomizerContext() {
auto& world = instance().m_archiWorld;
// Set hint texts before writing context
randomizer::logic::hints::GenerateAllHints(world);
// TODO: generate archipelago item get text replacements
auto randoData = WriteSeedData(world.get());
randoData.mHash = GetArchipelagoSeedName();
randomizer_GetContext() = randoData;
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
auto writeToFileResult = randoData.WriteToFile(workingDir / "seed.dat");
if (writeToFileResult.has_value()) {
DuskLog.error("Failed to create Rando Data. Reason: {}", writeToFileResult.value());
return;
}
}
void ArchipelagoContext::LoadRandomizerContext() {
randomizer_GetContext() = RandomizerContext();
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
randomizer_GetContext().LoadFromPath(workingDir / "seed.dat");
randomizer_GetContext().mHash = GetArchipelagoSeedName();
}
void ArchipelagoContext::GenerateLocalWorldData() {
bool createContext = false;
std::filesystem::path workingDir;
GetSeedDirectoryPath(workingDir);
if (std::filesystem::exists(workingDir)) {
instance().m_config.LoadFromFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
}else {
std::filesystem::create_directories(workingDir);
// creates base yamls at directory if they dont exist yet
instance().m_config.LoadFromFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
if (instance().m_SettingsFile.empty()) {
DuskLog.fatal("Settings Data was not sent to client! Unable to generate world data.");
return;
}
GenerateConfigFromAP(instance().m_config, instance().m_SettingsFile);
instance().m_config.WriteToFile(workingDir / "settings.yaml", workingDir / "preferences.yaml");
createContext = true;
}
CreateArchipelagoWorld();
FillArchipelagoWorld();
if (createContext) {
CreateRandomizerContext();
}else {
LoadRandomizerContext();
}
}
} // dusk::archi
@@ -1,149 +0,0 @@
#pragma once
#include <mutex>
#include <string>
#include "Archipelago.h"
namespace dusk::archi
{
class ArchipelagoContext {
private:
struct TEMP_GameItemInfo {
int itemId = -1;
randomizer::logic::item::Importance importance = randomizer::logic::item::Importance::INVALID;
std::string itemName;
};
struct TEMP_GameLocationInfo {
int apId = -1;
std::string locName;
};
struct GameLocationInfo {
int itemId = -1;
std::string itemName;
std::string locationName;
int64_t apLocationId = -1;
bool collected = false;
};
std::vector<std::pair<int, bool>> m_receivedItemsQueue;
std::mutex m_queueMutex;
// Rando Data
randomizer::seedgen::config::Config m_config;
std::unique_ptr<randomizer::logic::world::World> m_archiWorld = nullptr;
bool m_isUpdateLocations = false;
bool m_isNeedResetInv = false;
bool m_isAllowUpdateLocations = false;
bool m_isEnableDeathLink = false;
// AP Data
std::unordered_map<std::string, GameLocationInfo> m_locationItemInfo;
std::map<int, bool> m_initLocationCollectState;
AP_RoomInfo m_roomInfo;
std::string m_SettingsFile;
bool m_isNeedPlayerDeath = false;
bool m_isFromDeathLink = false;
// TEMP
std::map<int, TEMP_GameItemInfo> m_apItemToGameItem;
std::vector<TEMP_GameLocationInfo> m_apLocToGameLoc;
void LoadTempItemInfo();
void LoadTempLocationInfo();
void itemRecvImpl(int id, bool notify);
int getItemIdFromApId(int apId);
std::string getLocationNameFromApId(int apId) const;
bool tryKillPlayer();
public:
ArchipelagoContext();
// Config Getters/Setters
static void SetServerIp(const std::string_view& ip, int file);
static void SetSlotName(const std::string_view& name, int file);
static void SetPassword(const std::string_view& pass, int file);
static const std::string& GetServerIp(int file);
static const std::string& GetSlotName(int file);
static const std::string& GetPassword(int file);
static std::string GetArchipelagoSeedName();
static void GetSeedDirectoryPath(std::filesystem::path& outPath);
static bool IsSeedHashArchipelago(const std::string& seedStr);
static bool IsCurrentSeedHash(const std::string& seedStr);
// Connection Handlers
static bool ConnectToServer(int file, bool isBlocking = false);
static void DisconnectFromServer();
static bool IsConnected();
// State Handlers
static void MessageThreadFunc();
static void Execute();
static void HandleItemReceived(AP_NetworkItem& id, bool notify);
static void HandleResetInventory();
static void HandleReceiveLocationScout(const std::vector<AP_NetworkItem>& items);
static void UpdateCheckedLocations();
static void SetNeedUpdateLocations(bool update);
static bool IsLocationChecked(int locId);
static void SetLocationChecked(int locId, bool collected);
static void UpdateLocationState(int locId, bool collected);
static void UpdateAllLocationState();
static bool IsReceivedLocationScouts();
static void TryHandleDeathLink();
static bool TryHandleGameComplete();
// State Requesters
static void RequestAllLocationScout(bool isHint = false);
static void RequestPlayerDeath(bool isDeathLink = false);
// AP -> Internal Rando Converters
static bool GenerateConfigFromAP(randomizer::seedgen::config::Config& config, const std::string& settingsStr);
static int GetItemAtLocation(const std::string& locName);
static int GetItemAtLocation(int locId);
static void CreateArchipelagoWorld();
static void FillArchipelagoWorld();
static void CreateRandomizerContext();
static void LoadRandomizerContext();
static void GenerateLocalWorldData();
};
} // dusk::archi
+129
View File
@@ -0,0 +1,129 @@
#include "midna_hair_color.hpp"
#include "dusk/settings.h"
#include <array>
#include <string_view>
#include <unordered_map>
namespace dusk::cosmetics {
static std::unordered_map<std::string_view, std::array<GXColor, 9>> const midnaHairColors = {
{"Default", {{
/*l_lNormalKColor*/ /*l_normalKColor*/ /*l_normalColor*/
{0xFF, 0xDC, 0x00}, {0xB4, 0x87, 0x00}, {0x50, 0x00, 0x00},
/*l_bigKColor*/ /*l_lBigColor*/ /*l_bigColor*/
{0x50, 0x00, 0x00}, {0xFF, 0x78, 0x00}, {0xFF, 0x64, 0x78},
/*l_lNormalKColor2*//*l_normalKColor2*/ /*l_lBigKColor2*/
{0x00, 0xC3, 0xEB}, {0xC3, 0xC3, 0x00}, {0xAA, 0xFF, 0xC3}}}},
{"Pink", {{
{0xF5, 0xCF, 0xF3}, {0xAD, 0x7F, 0x7F}, {0x1B, 0x00, 0x20},
{0x3C, 0x02, 0x58}, {0xE3, 0x72, 0xF2}, {0xE3, 0x5F, 0xF8},
{0xDD, 0x00, 0xEB}, {0xDD, 0x00, 0xC3}, {0xF6, 0x4C, 0xFF}}}},
{"Red", {{
{0xE4, 0x65, 0x41}, {0xA1, 0x3E, 0x22}, {0x21, 0x00, 0x00},
{0x4F, 0x02, 0x01}, {0xF0, 0x3A, 0x25}, {0xF0, 0x30, 0x8C},
{0xEB, 0x00, 0x00}, {0xEB, 0x00, 0x00}, {0xFF, 0x4F, 0x3A}}}},
{"Yellow", {{
{0x91, 0x83, 0x0E}, {0x66, 0x50, 0x07}, {0x0E, 0x0B, 0x00},
{0x24, 0x25, 0x02}, {0xCB, 0xB7, 0x00}, {0xCB, 0x99, 0x78},
{0xEB, 0xDE, 0x00}, {0xEB, 0xDE, 0x00}, {0xFF, 0xF8, 0xBF}}}},
{"Green", {{
{0x35, 0x79, 0x53}, {0x25, 0x4A, 0x2B}, {0x00, 0x0E, 0x05},
{0x0A, 0x29, 0x10}, {0x00, 0xB6, 0x6F}, {0x00, 0x98, 0xB3},
{0x1F, 0xEB, 0x00}, {0x1F, 0xEB, 0x00}, {0x9A, 0xFF, 0x81}}}},
{"Blue", {{
{0x00, 0x72, 0xFF}, {0x00, 0x46, 0x85}, {0x00, 0x08, 0x28},
{0x16, 0x1B, 0x5D}, {0x00, 0x60, 0xFF}, {0x00, 0x50, 0xFF},
{0x00, 0x48, 0xEB}, {0x00, 0x48, 0xC3}, {0x3A, 0x66, 0xFF}}}},
{"Purple", {{
{0x6F, 0x34, 0xFF}, {0x4E, 0x20, 0x85}, {0x0D, 0x00, 0x34},
{0x15, 0x08, 0x79}, {0x62, 0x00, 0xFF}, {0x62, 0x00, 0xFF},
{0x7B, 0x00, 0xEB}, {0x7B, 0x00, 0xC3}, {0x94, 0x3E, 0xFF}}}},
{"Brown", {{
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x1A, 0x05, 0x00},
{0x3C, 0x19, 0x0E}, {0x3F, 0x1D, 0x0B}, {0x3F, 0x18, 0x7E},
{0x3F, 0x1D, 0x0B}, {0x3F, 0x1D, 0x09}, {0x59, 0x32, 0x1E}}}},
{"White", {{
{0xF0, 0xF1, 0xF1}, {0xA9, 0x94, 0x7E}, {0x09, 0x0B, 0x0C},
{0x22, 0x24, 0x24}, {0xFF, 0xFF, 0xFF}, {0xFF, 0xD5, 0xFF},
{0xEA, 0xEA, 0xEA}, {0xEA, 0xEA, 0xC2}, {0xF3, 0xF3, 0xF3}}}},
{"Black", {{
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x0B, 0x0B, 0x0B},
{0x23, 0x23, 0x23}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x78},
{0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}, {0x00, 0x00, 0x00}}}},
};
static struct {
GXColorS10 l_normalColor = { 0x50, 0x00, 0x00, 0x00 };
GXColor l_normalKColor = { 0xB4, 0x87, 0x00, 0x00 };
GXColor l_normalKColor2 = { 0x00, 0xC3, 0xC3, 0x00 };
GXColorS10 l_bigColor = { 0xFF, 0x64, 0x78, 0x00 };
GXColor l_bigKColor = { 0x1E, 0x00, 0x00, 0x00 };
GXColor l_lNormalKColor = { 0xFF, 0xDC, 0x00, 0x00 };
GXColor l_lNormalKColor2 = { 0x00, 0xC3, 0xEB, 0x00 };
GXColorS10 l_lBigColor = { 0xFF, 0x78, 0x00, 0x00 };
GXColor l_lBigKColor2 = { 0xAA, 0xFF, 0xC3, 0x00 };
} currentMidnaHairColors;
void set_all_midna_hair_colors() {
const auto& hairBaseColor = getSettings().cosmetics.midnaHairBaseColor.getValue();
const auto& hairTipsColor = getSettings().cosmetics.midnaHairTipsColor.getValue();
// Don't set colors if either is invalid
if (!midnaHairColors.contains(hairBaseColor) || !midnaHairColors.contains(hairTipsColor)) {
return;
}
// Colors we have to convert to GXColorS10
auto& normalColor = midnaHairColors.at(hairBaseColor)[2];
auto& bigColor = midnaHairColors.at(hairBaseColor)[5];
auto& lBigColor = midnaHairColors.at(hairBaseColor)[4];
currentMidnaHairColors.l_normalColor = GXColorS10{normalColor.r, normalColor.g, normalColor.b};
currentMidnaHairColors.l_normalKColor = midnaHairColors.at(hairBaseColor)[1];
currentMidnaHairColors.l_normalKColor2 = midnaHairColors.at(hairTipsColor)[7];
currentMidnaHairColors.l_bigColor = GXColorS10{bigColor.r, bigColor.g, bigColor.b};
currentMidnaHairColors.l_bigKColor = midnaHairColors.at(hairBaseColor)[3];
currentMidnaHairColors.l_lNormalKColor = midnaHairColors.at(hairBaseColor)[0];
currentMidnaHairColors.l_lNormalKColor2 = midnaHairColors.at(hairTipsColor)[6];
currentMidnaHairColors.l_lBigColor = GXColorS10{lBigColor.r, lBigColor.g, lBigColor.b};
currentMidnaHairColors.l_lBigKColor2 = midnaHairColors.at(hairTipsColor)[8];
}
const GXColorS10* get_midna_hair_normalColor() {
return &currentMidnaHairColors.l_normalColor;
}
const GXColor* get_midna_hair_normalKColor() {
return &currentMidnaHairColors.l_normalKColor;
}
const GXColor* get_midna_hair_normalKColor2() {
return &currentMidnaHairColors.l_normalKColor2;
}
const GXColorS10* get_midna_hair_bigColor() {
return &currentMidnaHairColors.l_bigColor;
}
const GXColor* get_midna_hair_bigKColor() {
return &currentMidnaHairColors.l_bigKColor;
}
const GXColor* get_midna_hair_lNormalKColor() {
return &currentMidnaHairColors.l_lNormalKColor;
}
const GXColor* get_midna_hair_lNormalKColor2() {
return &currentMidnaHairColors.l_lNormalKColor2;
}
const GXColorS10* get_midna_hair_lBigColor() {
return &currentMidnaHairColors.l_lBigColor;
}
const GXColor* get_midna_hair_lBigKColor2() {
return &currentMidnaHairColors.l_lBigKColor2;
}
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "dolphin/gx/GXStruct.h"
namespace dusk::cosmetics {
void set_all_midna_hair_colors();
const GXColorS10* get_midna_hair_normalColor();
const GXColor* get_midna_hair_normalKColor();
const GXColor* get_midna_hair_normalKColor2();
const GXColorS10* get_midna_hair_bigColor();
const GXColor* get_midna_hair_bigKColor();
const GXColor* get_midna_hair_lNormalKColor();
const GXColor* get_midna_hair_lNormalKColor2();
const GXColorS10* get_midna_hair_lBigColor();
const GXColor* get_midna_hair_lBigKColor2();
}
-60
View File
@@ -1,60 +0,0 @@
#include <dusk/imgui/ImGuiArchipelagoDebug.h>
#include "imgui.h"
#include "Archipelago.h"
#include "aurora/lib/window.hpp"
#include "dusk/file_select.hpp"
#include "dusk/archipelago/archipelago_context.hpp"
#include "dusk/ui/rando_seed_generation.hpp"
namespace dusk {
ImGuiArchipelagoDebug::ImGuiArchipelagoDebug() {
}
void ImGuiArchipelagoDebug::drawMenuItem() {
if (ImGui::BeginMenu("Randomizer")) {
ImGui::Checkbox("Archipelago Window", &m_drawWindow);
ImGui::EndMenu();
}
}
void ImGuiArchipelagoDebug::drawWindow() {
if (!m_drawWindow)
return;
if (!m_hasInitValues) {
m_hasInitValues = true;
}
ImGui::Begin("Archipelago Debug", &m_drawWindow);
if (archi::ArchipelagoContext::IsConnected()) {
if (ImGui::Button("Test Create World Data")) {
archi::ArchipelagoContext::GenerateLocalWorldData();
}
if (ImGui::Button("Disconnect")) {
archi::ArchipelagoContext::DisconnectFromServer();
}
}else {
if (ImGui::Button("Connect")) {
archi::ArchipelagoContext::ConnectToServer(dComIfGs_getDataNum());
}
}
ImGui::SeparatorText("Debug Buttons");
static bool isSimulateDeathLink = false;
if (ImGui::Button("Request Death Link")) {
archi::ArchipelagoContext::RequestPlayerDeath(isSimulateDeathLink);
}
ImGui::SameLine();
ImGui::Checkbox("Simulate Death Link", &isSimulateDeathLink);
ImGui::End();
}
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
namespace dusk {
class ImGuiArchipelagoDebug {
private:
bool m_drawWindow = false;
bool m_showPassword = false;
bool m_hasInitValues = false;
char m_serverIpInputBuffer[0x40] = {};
char m_serverPassInputBuffer[0x40] = {};
char m_slotNameInputBuffer[0x40] = {};
public:
ImGuiArchipelagoDebug();
void drawMenuItem();
void drawWindow();
};
}
-2
View File
@@ -278,7 +278,6 @@ namespace dusk {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuRandomizer.draw();
m_menuArchipelago.drawMenuItem();
m_menuTools.draw();
ImGui::EndMainMenuBar();
@@ -376,7 +375,6 @@ namespace dusk {
m_menuRandomizer.windowRandoGeneration();
m_menuRandomizer.windowRandoTracker();
m_menuArchipelago.drawWindow();
}
void ImGuiConsole::PostDraw() {
-2
View File
@@ -11,7 +11,6 @@
#include "ImGuiMenuRandomizer.hpp"
#include "dusk/main.h"
#include "imgui.h"
#include "ImGuiArchipelagoDebug.h"
union SDL_Event;
struct ImGuiWindow;
@@ -35,7 +34,6 @@ private:
ImVec2 m_dragScrollLastMousePos = {};
ImGuiMenuRandomizer m_menuRandomizer;
ImGuiArchipelagoDebug m_menuArchipelago;
// Keep always last
ImGuiMenuTools m_menuTools;
+2 -12
View File
@@ -20,8 +20,6 @@
#include <numeric>
#include <ranges>
#include "dusk/archipelago/archipelago_context.hpp"
namespace dusk {
@@ -256,16 +254,8 @@ namespace dusk {
auto trackerHash = trackerRando->GetConfig().GetHash(false);
// If no hash, or seeds switched, try to create tracker world from currently active seed
if (trackerHash.empty() || (trackerHash != contextHash && !contextHash.empty())) {
if (archi::ArchipelagoContext::IsConnected()) {
std::filesystem::path workingDir;
archi::ArchipelagoContext::GetSeedDirectoryPath(workingDir);
*trackerRando = randomizer::Randomizer(workingDir);
trackerRando->GenerateTrackerWorld(false);
}else {
*trackerRando = randomizer::Randomizer(ui::GetRandomizerPath());
trackerRando->GenerateTrackerWorld();
}
*trackerRando = randomizer::Randomizer(ui::GetRandomizerPath());
trackerRando->GenerateTrackerWorld();
}
if (randomizer_IsActive()) {
+1 -1
View File
@@ -314,7 +314,7 @@ void end_game_frame() noexcept {
}
void begin_context(Context context) noexcept {
if (context == Context::None) {
if (context == Context::None || !enabled()) {
return;
}
+74 -52
View File
@@ -1,54 +1,69 @@
#include "dusk/mouse.h"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include "dusk/menu_pointer.h"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
#include "d/actor/d_a_alink.h"
#include "d/d_com_inf_game.h"
#include <aurora/lib/window.hpp>
#include <imgui.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_video.h>
#include <aurora/lib/window.hpp>
#include <imgui.h>
#include <chrono>
namespace dusk::mouse {
namespace {
constexpr float kMousePixelToRad = 0.0025f;
constexpr int kIdleHideFrames = 99; // Approx. 3 seconds with 33ms ticks
using Clock = std::chrono::steady_clock;
float s_aim_yaw_rad = 0.0f;
float s_aim_pitch_rad = 0.0f;
float s_camera_yaw_rad = 0.0f;
constexpr float kMousePixelToRad = 0.0025f;
constexpr auto kCursorIdleDuration = std::chrono::seconds(1);
float s_aim_yaw_rad = 0.0f;
float s_aim_pitch_rad = 0.0f;
float s_camera_yaw_rad = 0.0f;
float s_camera_pitch_rad = 0.0f;
int s_idle_frames = 0;
Clock::time_point s_last_cursor_motion = Clock::now();
void reset_deltas() {
s_aim_yaw_rad = s_aim_pitch_rad = 0.0f;
s_camera_yaw_rad = s_camera_pitch_rad = 0.0f;
}
bool queryMouseAimContext() {
bool query_mouse_aim_context() {
return getSettings().game.enableMouseAim.getValue() && dCamera_c::isAimActive();
}
bool wantMouseCapture() {
return getSettings().game.enableMouseCamera.getValue() || queryMouseAimContext();
bool want_mouse_capture() {
return getSettings().game.enableMouseCamera.getValue() || query_mouse_aim_context();
}
bool isWindowFocused(SDL_Window* window) {
bool mouse_input_enabled() {
const auto& game = getSettings().game;
return game.enableMouseAim.getValue() || game.enableMouseCamera.getValue();
}
bool is_window_focused(SDL_Window* window) {
if (window == nullptr) {
return false;
}
return (SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
bool shouldCaptureMouse(SDL_Window* window) {
if (window == nullptr || ui::any_document_visible() || menu_pointer::active()) {
return false;
}
return wantMouseCapture() && isWindowFocused(window);
bool imgui_windows_visible() {
return ImGui::GetIO().MetricsRenderWindows > 0;
}
bool syncCaptureState(SDL_Window* window, bool should_capture) {
bool should_capture_mouse(SDL_Window* window) {
if (window == nullptr || ui::any_document_visible() || imgui_windows_visible() ||
menu_pointer::active())
{
return false;
}
return want_mouse_capture() && is_window_focused(window);
}
bool sync_capture_state(SDL_Window* window, bool should_capture) {
if (window == nullptr) {
reset_deltas();
return false;
@@ -78,7 +93,7 @@ bool syncCaptureState(SDL_Window* window, bool should_capture) {
return is_captured;
}
void accumulateDeltas(float mx_rel, float my_rel, bool camera_active, bool aim_active) {
void accumulate_deltas(float mx_rel, float my_rel, bool camera_active, bool aim_active) {
const auto& game = getSettings().game;
const bool mirror_mode = game.enableMirrorMode.getValue();
const bool invert_y = game.invertMouseY.getValue();
@@ -114,57 +129,62 @@ void set_cursor_visible(bool visible) {
}
}
void update_cursor_visibility(SDL_Window* window, bool captured) {
if (window == nullptr || !isWindowFocused(window)) {
return;
}
bool cursor_idle() {
return Clock::now() - s_last_cursor_motion >= kCursorIdleDuration;
}
bool should_show_cursor(bool captured) {
if (captured) {
s_idle_frames = 0;
set_cursor_visible(false);
return false;
}
if (ui::any_document_visible()) {
return true;
}
if (imgui_windows_visible()) {
return true;
}
if (menu_pointer::enabled() && menu_pointer::active()) {
return true;
}
if (mouse_input_enabled()) {
return false;
}
return !cursor_idle();
}
void update_cursor_visibility(SDL_Window* window, bool captured) {
if (window == nullptr || !is_window_focused(window)) {
return;
}
const ImGuiIO& io = ImGui::GetIO();
if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) {
s_idle_frames = 0;
set_cursor_visible(true);
return;
}
if (s_idle_frames < kIdleHideFrames) {
++s_idle_frames;
set_cursor_visible(true);
} else {
set_cursor_visible(false);
}
set_cursor_visible(should_show_cursor(captured));
}
} // namespace
void read() {
SDL_Window* window = aurora::window::get_sdl_window();
const bool capture_active = syncCaptureState(window, shouldCaptureMouse(window));
const bool capture_active = sync_capture_state(window, should_capture_mouse(window));
update_cursor_visibility(window, capture_active);
if (!capture_active) {
return;
}
const bool aim_active = capture_active && queryMouseAimContext();
const bool aim_active = capture_active && query_mouse_aim_context();
const bool camera_active = capture_active && getSettings().game.enableMouseCamera;
float mx_rel = 0.0f;
float my_rel = 0.0f;
SDL_GetRelativeMouseState(&mx_rel, &my_rel);
accumulateDeltas(mx_rel, my_rel, camera_active, aim_active);
accumulate_deltas(mx_rel, my_rel, camera_active, aim_active);
}
void getAimDeltas(float& out_yaw, float& out_pitch) {
void get_aim_deltas(float& out_yaw, float& out_pitch) {
out_yaw = s_aim_yaw_rad;
out_pitch = s_aim_pitch_rad;
}
void getCameraDeltas(float& out_yaw, float& out_pitch) {
void get_camera_deltas(float& out_yaw, float& out_pitch) {
out_yaw = 0.0f;
out_pitch = 0.0f;
@@ -178,26 +198,28 @@ void getCameraDeltas(float& out_yaw, float& out_pitch) {
void handle_event(const SDL_Event& event) noexcept {
switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
s_last_cursor_motion = Clock::now();
break;
case SDL_EVENT_WINDOW_FOCUS_LOST:
onFocusLost();
on_focus_lost();
break;
case SDL_EVENT_WINDOW_FOCUS_GAINED:
onFocusGained();
on_focus_gained();
break;
}
}
void onFocusLost() {
void on_focus_lost() {
SDL_Window* window = aurora::window::get_sdl_window();
if (window != nullptr) {
syncCaptureState(window, false);
sync_capture_state(window, false);
}
s_idle_frames = 0;
set_cursor_visible(true);
}
void onFocusGained() {
void on_focus_gained() {
SDL_Window* window = aurora::window::get_sdl_window();
syncCaptureState(window, shouldCaptureMouse(window));
sync_capture_state(window, should_capture_mouse(window));
}
} // namespace dusk::mouse
@@ -22,14 +22,10 @@
#include "d/d_meter2_draw.h"
#include "d/d_meter2_info.h"
#include "d/d_msg_flow.h"
#include "dusk/archipelago/archipelago_context.hpp"
std::optional<std::string> RandomizerContext::WriteToFile() {
return WriteToFile(this->GetSeedDataPath());
}
std::optional<std::string> RandomizerContext::WriteToFile(const fspath& path) {
std::ofstream seedData(path);
std::ofstream seedData(this->GetSeedDataPath());
if (!seedData.is_open()) {
return "Could not open seed data file";
}
@@ -145,11 +141,7 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
return std::nullopt;
}
return LoadFromPath(this->GetSeedDataPath());
}
std::optional<std::string> RandomizerContext::LoadFromPath(const fspath& path) {
auto in = LoadYAML(path);
auto in = LoadYAML(this->GetSeedDataPath());
// Necessary settings
for (const auto& settingNode : in["mSettings"] ) {
@@ -609,8 +601,6 @@ int RandomizerState::execute() {
handleFoolishItem();
}
dusk::archi::ArchipelagoContext::Execute();
return 1;
}
@@ -1046,11 +1036,6 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
// Set data for all locations
for (const auto& location : world->GetAllLocations()) {
// skip locations with nothing
if (location->GetCurrentItem()->GetID() == -1) {
continue;
}
const auto& metaData = location->GetMetadata();
// Chest Overrides
@@ -81,9 +81,7 @@ public:
std::unordered_map<int, EntranceOverride> mEntranceOverrides{};
std::optional<std::string> WriteToFile();
std::optional<std::string> WriteToFile(const fspath& path);;
std::optional<std::string> LoadFromHash(const std::string& hash);
std::optional<std::string> LoadFromPath(const fspath& path);
std::filesystem::path GetSeedDataPath() const;
enum Settings {
@@ -279,9 +277,4 @@ u32 getStageObjCRC32(u8* data, size_t size);
*/
bool GenerateAndWriteSeed(std::string& generationStatusMsg);
/*
* Creates RandomizerContext that contains all needed data for the seed.
*/
RandomizerContext WriteSeedData(randomizer::logic::world::World* world);
#endif //DUSK_RANDOMIZER_CONTEXT_HPP
-76
View File
@@ -495,84 +495,8 @@ randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world:
return pool;
}
void setLocationCollected(randomizer::logic::location::Location* location, bool collect) {
auto& locationMeta = location->GetMetadata();
if (auto& chestNode = locationMeta["Chest"]) {
auto tboxId = chestNode[0]["Tbox Id"].as<u8>();
auto stageId = getStageSaveId(chestNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageTbox(stageId, tboxId);
else
dComIfGs_offStageTbox(stageId, tboxId);
}
if (auto& poeNode = locationMeta["Poe"]) {
auto flag = poeNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(poeNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageSwitch(stageId, flag);
else
dComIfGs_offStageSwitch(stageId, flag);
}
if (auto& freeStandingItemNode = locationMeta["Freestanding Item"]) {
auto flag = freeStandingItemNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(freeStandingItemNode[0]["Stage"].as<u8>());
// big baba uses tbox, hardcode this edge case
if (location->GetName() == "Forest Temple Big Baba Key") {
if (collect)
dComIfGs_onStageTbox(stageId, flag);
else
dComIfGs_offStageTbox(stageId, flag);
}else {
if (collect)
dComIfGs_onStageItem(stageId, flag);
else
dComIfGs_offStageItem(stageId, flag);
}
}
if (auto& eventFlagNode = locationMeta["Event Flag"]) {
auto flag = eventFlagNode.as<u16>();
if (collect)
dComIfGs_onEventBit(flag);
else
dComIfGs_offEventBit(flag);
}
if (auto& wolfNode = locationMeta["Golden Wolf"]) {
auto flag = wolfNode[0]["Flag"].as<u16>();
if (collect)
dComIfGs_onEventBit(flag);
else
dComIfGs_offEventBit(flag);
}
if (auto& switchFlagNode = locationMeta["Switch Flag"]) {
auto flag = switchFlagNode["Flag"].as<u8>();
auto stageId = getStageSaveId(switchFlagNode["Stage"].as<u8>());
if (collect)
dComIfGs_onStageSwitch(stageId, flag);
else
dComIfGs_offStageSwitch(stageId, flag);
}
if (auto& itemFlagNode = locationMeta["Item Flag"]) {
auto flag = itemFlagNode["Flag"].as<u8>();
auto stageId = getStageSaveId(itemFlagNode["Stage"].as<u8>());
if (collect)
dComIfGs_onStageItem(stageId, flag);
else
dComIfGs_offStageItem(stageId, flag);
}
if (auto& twilitInsectNode = locationMeta["Twilit Insect"]) {
auto flag = twilitInsectNode[0]["Flag"].as<u8>();
auto stageId = getStageSaveId(twilitInsectNode[0]["Stage"].as<u8>());
if (collect)
dComIfGs_onStageTbox(stageId, flag);
else
dComIfGs_offStageTbox(stageId, flag);
}
}
bool isLocationObtained(randomizer::logic::location::Location* location) {
auto& locationMeta = location->GetMetadata();
if (auto& chestNode = locationMeta["Chest"]) {
auto tboxId = chestNode[0]["Tbox Id"].as<u8>();
auto stageId = getStageSaveId(chestNode[0]["Stage"].as<u8>());
-5
View File
@@ -25,11 +25,6 @@ int getTempleKeysFound(int saveId);
*/
randomizer::logic::item_pool::ItemPool getSaveItemPool(randomizer::logic::world::World* world);
/*
* Updates locations relevant flag in save to whatever state is supplied.
*/
void setLocationCollected(randomizer::logic::location::Location* location, bool collect);
/*
* Finds locations relevant flag in save (using its metadata) and checks if it's been set.
*/
+3 -151
View File
@@ -11,37 +11,30 @@
- Name: Green Rupee
Importance: Junk
Id: 0x01
APItemId: 0
- Name: Blue Rupee
Importance: Junk
Id: 0x02
APItemId: 1
- Name: Yellow Rupee
Importance: Junk
Id: 0x03
APItemId: 2
- Name: Red Rupee
Importance: Junk
Id: 0x04
APItemId: 3
- Name: Purple Rupee
Importance: Junk
Id: 0x05
APItemId: 4
- Name: Orange Rupee
Importance: Junk
Id: 0x06
APItemId: 5
- Name: Silver Rupee
Importance: Junk
Id: 0x07
APItemId: 6
#- Name: Unused (Gives text about combining bombs)
# Importance:
@@ -54,37 +47,30 @@
- Name: Bombs 5
Importance: Junk
Id: 0x0A
APItemId: 8
- Name: Bombs 10
Importance: Junk
Id: 0x0B
APItemId: 9
- Name: Bombs 20
Importance: Junk
Id: 0x0C
APItemId: 10
- Name: Bombs 30
Importance: Junk
Id: 0x0D
APItemId: 11
- Name: Arrows 10
Importance: Junk
Id: 0x0E
APItemId: 12
- Name: Arrows 20
Importance: Junk
Id: 0x0F
APItemId: 13
- Name: Arrows 30
Importance: Junk
Id: 0x10
APItemId: 14
#- Name: Arrows 1
# Importance: Junk
@@ -93,57 +79,46 @@
- Name: Seeds 50
Importance: Junk
Id: 0x12
APItemId: 15
- Name: Foolish Item # Custom Rando Item
Importance: Junk
Id: 0x13
APItemId: 131
- Name: Ordon Spring Portal # Custom Rando item
Importance: Major
Id: 0x14
APItemId: -1
- Name: South Faron Portal # Custom Rando Item
Importance: Major
Id: 0x15
APItemId: -1
- Name: Water Bombs 5
Importance: Junk
Id: 0x16
APItemId: 17
- Name: Water Bombs 10
Importance: Junk
Id: 0x17
APItemId: 18
- Name: Water Bombs 15
Importance: Junk
Id: 0x18
APItemId: 19
#- Name: Water Bombs 3
# Importance: Junk
# Id: 0x19
# APItemId: 16
- Name: Bomblings 5
Importance: Junk
Id: 0x1A
APItemId: 21
- Name: Bomblings 10
Importance: Junk
Id: 0x1B
APItemId: 22
#- Name: Bomblings 3
# Importance: Junk
# Id: 0x1C
# APItemId: 20
#
#- Name: Bomblings 1
# Importance: Junk
@@ -164,12 +139,10 @@
- Name: Piece of Heart
Importance: Junk
Id: 0x21
APItemId: 23
- Name: Heart Container
Importance: Junk
Id: 0x22
APItemId: 24
#- Name: Dungeon Map
# Importance: Junk
@@ -202,17 +175,14 @@
- Name: Ordon Shield
Importance: Major
Id: 0x2A
APItemId: 26
- Name: Wooden Shield
Importance: Junk
Id: 0x2B
APItemId: 26
- Name: Hylian Shield
Importance: Major
Id: 0x2C
APItemId: 27
#- Name: Ooccoo's Note
# Importance: Junk
@@ -229,17 +199,14 @@
- Name: Magic Armor
Importance: Major
Id: 0x30
APItemId: 28
- Name: Zora Armor
Importance: Major
Id: 0x31
APItemId: 29
- Name: Shadow Crystal
Importance: Major
Id: 0x32
APItemId: 30
#- Name: Ooccoo Dungeon
# Importance: Junk
@@ -252,7 +219,6 @@
- Name: Progressive Wallet # Also Large Wallet
Importance: Major
Id: 0x35
APItemId: 31
#- Name: Giant Wallet
# Importance: Major
@@ -269,22 +235,18 @@
- Name: Upper Zoras River Portal
Importance: Major
Id: 0x39
APItemId: -1
- Name: Castle Town Portal
Importance: Major
Id: 0x3A
APItemId: -1
- Name: Gerudo Desert Portal
Importance: Major
Id: 0x3B
APItemId: -1
- Name: North Faron Portal
Importance: Major
Id: 0x3C
APItemId: -1
#- Name: Coral Earring
# Importance: Major
@@ -293,47 +255,38 @@
- Name: Hawkeye
Importance: Minor
Id: 0x3E
APItemId: 32
- Name: Progressive Sword # Also Wooden Sword
Importance: Major
Id: 0x3F
APItemId: 25
- Name: Gale Boomerang
Importance: Major
Id: 0x40
APItemId: 33
- Name: Spinner
Importance: Major
Id: 0x41
APItemId: 34
- Name: Ball and Chain
Importance: Major
Id: 0x42
APItemId: 35
- Name: Progressive Bow
Importance: Major
Id: 0x43
APItemId: 36
- Name: Progressive Clawshot
Importance: Major
Id: 0x44
APItemId: 37
- Name: Iron Boots
Importance: Major
Id: 0x45
APItemId: 38
- Name: Progressive Dominion Rod
Importance: Major
Id: 0x46
APItemId: 39
#- Name: Double Clawshots
# Importance: Major
@@ -342,7 +295,6 @@
- Name: Lantern
Importance: Major
Id: 0x48
APItemId: 40
#- Name: Master Sword with Light
# Importance: Major
@@ -351,12 +303,10 @@
- Name: Progressive Fishing Rod
Importance: Major
Id: 0x4A
APItemId: 41
- Name: Slingshot
Importance: Major
Id: 0x4B
APItemId: 44
#- Name: Dominion Rod Uncharged
# Importance: Major
@@ -365,22 +315,18 @@
- Name: Kakariko Gorge Portal
Importance: Major
Id: 0x4D
APItemId: -1
- Name: Kakariko Village Portal
Importance: Major
Id: 0x4E
APItemId: -1
- Name: Giant Bomb Bag
Importance: Minor
Id: 0x4F
APItemId: 133
- Name: Bomb Bag
Importance: Major
Id: 0x50
APItemId: 45
#- Name: Also Bomb Bag (different text)
# Importance: Major
@@ -389,7 +335,6 @@
- Name: Death Mountain Portal
Importance: Major
Id: 0x52
APItemId: -1
#- Name: Light Arrow
# Importance: Major
@@ -410,7 +355,6 @@
- Name: Zoras Domain Portal
Importance: Major
Id: 0x57
APItemId: -1
#- Name: Fising Rod Lure
# Importance: Junk
@@ -447,12 +391,10 @@
- Name: Empty Bottle
Importance: Major
Id: 0x60
APItemId: 46
- Name: Red Potion Shop
Importance: Junk
Id: 0x61
APItemId: -1
#- Name: Green Potion Shop
# Importance: Junk
@@ -461,7 +403,6 @@
- Name: Blue Potion Shop
Importance: Junk
Id: 0x63
APItemId: -1
#- Name: Milk
# Importance: Junk
@@ -470,7 +411,6 @@
- Name: Bottle with Half Milk
Importance: Major
Id: 0x65
APItemId: 47
#- Name: Lantern Oil Shop
# Importance: Junk
@@ -527,7 +467,6 @@
- Name: Fairy Tears
Importance: Junk
Id: 0x73
APItemId: -1
#- Name: Worm
# Importance: Junk
@@ -536,7 +475,6 @@
- Name: Bottle with Great Fairies Tears
Importance: Major
Id: 0x75
APItemId: 49
#- Name: Bee Larva Scooped
# Importance: Junk
@@ -581,161 +519,133 @@
- Name: Renados Letter
Importance: Major
Id: 0x80
APItemId: -1
- Name: Invoice
Importance: Major
Id: 0x81
APItemId: -1
- Name: Wooden Statue
Importance: Major
Id: 0x82
APItemId: -1
- Name: Ilias Charm
Importance: Major
Id: 0x83
APItemId: -1
- Name: Horse Call
Importance: Minor
Id: 0x84
APItemId: 53
- Name: Forest Temple Small Key
Importance: Major
Id: 0x85
Dungeon Small Key: Forest Temple
APItemId: 54
- Name: Goron Mines Small Key
Importance: Major
Id: 0x86
Dungeon Small Key: Goron Mines
APItemId: 55
- Name: Lakebed Temple Small Key
Importance: Major
Id: 0x87
Dungeon Small Key: Lakebed Temple
APItemId: 56
- Name: Arbiters Grounds Small Key
Importance: Major
Id: 0x88
Dungeon Small Key: Arbiters Grounds
APItemId: 57
- Name: Snowpeak Ruins Small Key
Importance: Major
Id: 0x89
Dungeon Small Key: Snowpeak Ruins
APItemId: 58
- Name: Temple of Time Small Key
Importance: Major
Id: 0x8A
Dungeon Small Key: Temple of Time
APItemId: 59
- Name: City in the Sky Small Key
Importance: Major
Id: 0x8B
Dungeon Small Key: City in the Sky
APItemId: 60
- Name: Palace of Twilight Small Key
Importance: Major
Id: 0x8C
Dungeon Small Key: Palace of Twilight
APItemId: 61
- Name: Hyrule Castle Small Key
Importance: Major
Id: 0x8D
Dungeon Small Key: Hyrule Castle
APItemId: 62
- Name: Gerudo Desert Bulblin Camp Key
Importance: Major
Id: 0x8E
APItemId: 66
- Name: Lake Hylia Portal
Importance: Major
Id: 0x8F
APItemId: -1
- Name: Aurus Memo
Importance: Major
Id: 0x90
APItemId: 67
- Name: Asheis Sketch
Importance: Major
Id: 0x91
APItemId: 68
- Name: Forest Temple Big Key
Importance: Major
Id: 0x92
Dungeon Big Key: Forest Temple
APItemId: 69
- Name: Lakebed Temple Big Key
Importance: Major
Id: 0x93
Dungeon Big Key: Lakebed Temple
APItemId: 70
- Name: Arbiters Grounds Big Key
Importance: Major
Id: 0x94
Dungeon Big Key: Arbiters Grounds
APItemId: 72
- Name: Temple of Time Big Key
Importance: Major
Id: 0x95
Dungeon Big Key: Temple of Time
APItemId: 74
- Name: City in the Sky Big Key
Importance: Major
Id: 0x96
Dungeon Big Key: City in the Sky
APItemId: 75
- Name: Palace of Twilight Big Key
Importance: Major
Id: 0x97
Dungeon Big Key: Palace of Twilight
APItemId: 76
- Name: Hyrule Castle Big Key
Importance: Major
Id: 0x98
Dungeon Big Key: Hyrule Castle
APItemId: 77
- Name: Forest Temple Compass
Importance: Junk
Id: 0x99
Dungeon Compass: Forest Temple
APItemId: 78
- Name: Goron Mines Compass
Importance: Junk
Id: 0x9A
Dungeon Compass: Goron Mines
APItemId: 79
- Name: Lakebed Temple Compass
Importance: Junk
Id: 0x9B
Dungeon Compass: Lakebed Temple
APItemId: 80
#- Name: Lantern Yellow Chu Chu
# Importance: Junk
@@ -744,7 +654,6 @@
- Name: Bottle with Lantern Oil
Importance: Major
Id: 0x9D
APItemId: 48
#- Name: Bee Larva Shop
# Importance: Junk
@@ -777,7 +686,6 @@
- Name: Progressive Mirror Shard
Importance: Major
Id: 0xA5
APItemId: 120
#- Name: Mirror Piece 3
# Importance: Junk
@@ -791,47 +699,39 @@
Importance: Junk
Id: 0xA8
Dungeon Compass: Arbiters Grounds
APItemId: 81
- Name: Snowpeak Ruins Compass
Importance: Junk
Id: 0xA9
Dungeon Compass: Snowpeak Ruins
APItemId: 82
- Name: Temple of Time Compass
Importance: Junk
Id: 0xAA
Dungeon Compass: Temple of Time
APItemId: 83
- Name: City in the Sky Compass
Importance: Junk
Id: 0xAB
Dungeon Compass: City in the Sky
APItemId: 84
- Name: Palace of Twilight Compass
Importance: Junk
Id: 0xAC
Dungeon Compass: Palace of Twilight
APItemId: 85
- Name: Hyrule Castle Compass
Importance: Junk
Id: 0xAD
Dungeon Compass: Hyrule Castle
APItemId: 86
- Name: Mirror Chamber Portal
Importance: Major
Id: 0xAE
APItemId: -1
- Name: Snowpeak Portal
Importance: Major
Id: 0xAF
APItemId: -1
#- Name: Ilias Scent
# Importance: Junk
@@ -861,185 +761,150 @@
Importance: Junk
Id: 0xB6
Dungeon Map: Forest Temple
APItemId: 87
- Name: Goron Mines Dungeon Map
Importance: Junk
Id: 0xB7
Dungeon Map: Goron Mines
APItemId: 88
- Name: Lakebed Temple Dungeon Map
Importance: Junk
Id: 0xB8
Dungeon Map: Lakebed Temple
APItemId: 89
- Name: Arbiters Grounds Dungeon Map
Importance: Junk
Id: 0xB9
Dungeon Map: Arbiters Grounds
APItemId: 90
- Name: Snowpeak Ruins Dungeon Map
Importance: Junk
Id: 0xBA
Dungeon Map: Snowpeak Ruins
APItemId: 91
- Name: Temple of Time Dungeon Map
Importance: Junk
Id: 0xBB
Dungeon Map: Temple of Time
APItemId: 92
- Name: City in the Sky Dungeon Map
Importance: Junk
Id: 0xBC
Dungeon Map: City in the Sky
APItemId: 93
- Name: Palace of Twilight Dungeon Map
Importance: Junk
Id: 0xBD
Dungeon Map: Palace of Twilight
APItemId: 94
- Name: Hyrule Castle Dungeon Map
Importance: Junk
Id: 0xBE
Dungeon Compass: Hyrule Castle
APItemId: 95
- Name: Sacred Grove Portal
Importance: Major
Id: 0xBF
APItemId: -1
- Name: Male Beetle
Importance: Major
Id: 0xC0
APItemId: 96
- Name: Female Beetle
Importance: Major
Id: 0xC1
APItemId: 97
- Name: Male Butterfly
Importance: Major
Id: 0xC2
APItemId: 98
- Name: Female Butterfly
Importance: Major
Id: 0xC3
APItemId: 99
- Name: Male Stag Beetle
Importance: Major
Id: 0xC4
APItemId: 100
- Name: Female Stag Beetle
Importance: Major
Id: 0xC5
APItemId: 101
- Name: Male Grasshopper
Importance: Major
Id: 0xC6
APItemId: 102
- Name: Female Grasshopper
Importance: Major
Id: 0xC7
APItemId: 103
- Name: Male Phasmid
Importance: Major
Id: 0xC8
APItemId: 104
- Name: Female Phasmid
Importance: Major
Id: 0xC9
APItemId: 105
- Name: Male Pill Bug
Importance: Major
Id: 0xCA
APItemId: 106
- Name: Female Pill Bug
Importance: Major
Id: 0xCB
APItemId: 107
- Name: Male Mantis
Importance: Major
Id: 0xCC
APItemId: 108
- Name: Female Mantis
Importance: Major
Id: 0xCD
APItemId: 109
- Name: Male Ladybug
Importance: Major
Id: 0xCE
APItemId: 110
- Name: Female Ladybug
Importance: Major
Id: 0xCF
APItemId: 111
- Name: Male Snail
Importance: Major
Id: 0xD0
APItemId: 112
- Name: Female Snail
Importance: Major
Id: 0xD1
APItemId: 113
- Name: Male Dragonfly
Importance: Major
Id: 0xD2
APItemId: 114
- Name: Female Dragonfly
Importance: Major
Id: 0xD3
APItemId: 115
- Name: Male Ant
Importance: Major
Id: 0xD4
APItemId: 116
- Name: Female Ant
Importance: Major
Id: 0xD5
APItemId: 117
- Name: Male Dayfly
Importance: Major
Id: 0xD6
APItemId: 118
- Name: Female Dayfly
Importance: Major
Id: 0xD7
APItemId: 119
- Name: Progressive Fused Shadow
Importance: Major
Id: 0xD8
APItemId: 121
#- Name: Fused Shadow 2
# Importance: Junk
@@ -1053,10 +918,9 @@
# Importance: Junk
# Id: 0xDB
- Name: Archipelago Item
Importance: Minor
Id: 0xDC
APItemId: -1
#- Name: Unused
# Importance: Junk
# Id: 0xDC
#- Name: Unused
# Importance: Junk
@@ -1073,12 +937,10 @@
- Name: Poe Soul
Importance: Major
Id: 0xE0
APItemId: 124
- Name: Progressive Hidden Skill # Also Ending Blow
Importance: Major
Id: 0xE1
APItemId: 122
#- Name: Shield Attack
# Importance: Major
@@ -1107,12 +969,10 @@
- Name: Bridge of Eldin Portal
Importance: Major
Id: 0xE8
APItemId: -1
- Name: Progressive Sky Book
Importance: Major
Id: 0xE9
APItemId: 123
#- Name: Partially Filled Sky Book
# Importance: Major
@@ -1129,12 +989,10 @@
- Name: Purple Rupee Links House
Importance: Junk
Id: 0xED
APItemId: 7
- Name: North Faron Woods Gate Key
Importance: Major
Id: 0xEE
APItemId: 63
#- Name: Blue Fire
# Importance: Major
@@ -1155,23 +1013,19 @@
- Name: Gate Keys
Importance: Major
Id: 0xF3
APItemId: 65
- Name: Ordon Pumpkin
Importance: Major
Id: 0xF4
APItemId: 129
- Name: Ordon Cheese
Importance: Major
Id: 0xF5
APItemId: 130
- Name: Snowpeak Ruins Bedroom Key
Importance: Major
Id: 0xF6
Dungeon Big Key: Snowpeak Ruins
APItemId: 73
#- Name: Surfboard (Unused)
# Importance: Major
@@ -1185,7 +1039,6 @@
Importance: Major
Id: 0xF9
Dungeon Big Key: Goron Mines
APItemId: 71
#- Name: Got Lantern Back
# Importance: Major
@@ -1206,7 +1059,6 @@
- Name: Coro Key
Importance: Major
Id: 0xFE
APItemId: 64
#- Name: Invalid
# Importance: Major
File diff suppressed because it is too large Load Diff
@@ -288,14 +288,6 @@ Progressive Wallet:
Cryptic:
Text: a {money bag}
Archipelago Item:
Standard:
Text: Archipelago Item
Pretty:
Text: an {Archipelago Item}
Cryptic:
Text: an {item from another world}
Upper Zoras River Portal:
Standard:
Text: Upper Zoras River Portal
@@ -1612,11 +1604,6 @@ No Required Dungeons Text:
Text: No Required Dungeons
# ITEM GET TEXT
Archipelago Item Get Item Text:
Standard:
Text: |-
<fast>You got an <red>Archipelago Item<white>!
Foolish Get Item Text:
Standard:
Text: |-
@@ -288,14 +288,6 @@ Progressive Wallet:
Cryptic:
Text: a {money bag}
Archipelago Item:
Standard:
Text: Archipelago Item
Pretty:
Text: an {Archipelago Item}
Cryptic:
Text: an {item from another world}
Upper Zoras River Portal:
Standard:
Text: Upper Zoras River Portal
@@ -1612,11 +1604,6 @@ No Required Dungeons Text:
Text: No Required Dungeons
# ITEM GET TEXT
Archipelago Item Get Item Text:
Standard:
Text: |-
<fast>You got an <red>Archipelago Item<white>!
Foolish Get Item Text:
Standard:
Text: |-
@@ -288,14 +288,6 @@ Progressive Wallet:
Cryptic:
Text: a {money bag}
Archipelago Item:
Standard:
Text: Archipelago Item
Pretty:
Text: an {Archipelago Item}
Cryptic:
Text: an {item from another world}
Upper Zoras River Portal:
Standard:
Text: Upper Zoras River Portal
@@ -1612,11 +1604,6 @@ No Required Dungeons Text:
Text: No Required Dungeons
# ITEM GET TEXT
Archipelago Item Get Item Text:
Standard:
Text: |-
<fast>You got an <red>Archipelago Item<white>!
Foolish Get Item Text:
Standard:
Text: |-
@@ -288,14 +288,6 @@ Progressive Wallet:
Cryptic:
Text: a {money bag}
Archipelago Item:
Standard:
Text: Archipelago Item
Pretty:
Text: an {Archipelago Item}
Cryptic:
Text: an {item from another world}
Upper Zoras River Portal:
Standard:
Text: Upper Zoras River Portal
@@ -1612,11 +1604,6 @@ No Required Dungeons Text:
Text: No Required Dungeons
# ITEM GET TEXT
Archipelago Item Get Item Text:
Standard:
Text: |-
<fast>You got an <red>Archipelago Item<white>!
Foolish Get Item Text:
Standard:
Text: |-
@@ -288,14 +288,6 @@ Progressive Wallet:
Cryptic:
Text: a {money bag}
Archipelago Item:
Standard:
Text: Archipelago Item
Pretty:
Text: an {Archipelago Item}
Cryptic:
Text: an {item from another world}
Upper Zoras River Portal:
Standard:
Text: Upper Zoras River Portal
@@ -1612,11 +1604,6 @@ No Required Dungeons Text:
Text: No Required Dungeons
# ITEM GET TEXT
Archipelago Item Get Item Text:
Standard:
Text: |-
<fast>You got an <red>Archipelago Item<white>!
Foolish Get Item Text:
Standard:
Text: |-
@@ -11,10 +11,6 @@
# Group: 0
# Message Id: 122
- Name: Archipelago Item Get Item Text
Group: 0
Message Id: 321
- Name: Shadow Crystal Get Item Text
Group: 0
Message Id: 151
@@ -232,7 +232,7 @@ namespace randomizer::logic::entrance
return this->_decoupled;
}
void Entrance::SetDisabled(const bool& disabled)
void Entrance::SetDisbled(const bool& disabled)
{
this->_disabled = disabled;
LOG_TO_DEBUG(this->GetOriginalName() + " disabled status set to " + (disabled ? "True" : "False"));
@@ -102,7 +102,7 @@ namespace randomizer::logic::entrance
bool IsShuffled() const;
void SetDecoupled(const bool& decoupled);
bool IsDecoupled() const;
void SetDisabled(const bool& disabled);
void SetDisbled(const bool& disabled);
bool IsDisabled() const;
void SetPrimary(const bool& primary);
bool IsPrimary() const;
+55 -56
View File
@@ -4,33 +4,36 @@
#include "world.hpp"
namespace randomizer::logic::hints {
static const std::unordered_map<std::string, std::string> dungeonColors = {
{"Forest Temple", "<green>"},
{"Goron Mines", "<red>"},
{"Lakebed Temple", "<blue>"},
{"Arbiters Grounds", "<orange>"},
{"Snowpeak Ruins", "<light blue>"},
{"Temple of Time", "<dark green>"},
{"City in the Sky", "<yellow>"},
{"Palace of Twilight", "<purple>"},
// {"Hyrule Castle", "<silver>"}
};
// Tell the player which dungeons are required on the sign in front of Link's House
static void GenerateRequiredDungeonsHint(world::World* world) {
auto& requiredDungeonText = world->AddNewText("Links House Sign");
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
requiredDungeonText += dungeonColors.at(dungeonName) + getTextObject(dungeonName) + "\n";
}
}
static void GenerateRequiredDungeonsHint(world::WorldPool& worlds) {
static const std::unordered_map<std::string, std::string> dungeonColors = {
{"Forest Temple", "<green>"},
{"Goron Mines", "<red>"},
{"Lakebed Temple", "<blue>"},
{"Arbiters Grounds", "<orange>"},
{"Snowpeak Ruins", "<light blue>"},
{"Temple of Time", "<dark green>"},
{"City in the Sky", "<yellow>"},
{"Palace of Twilight", "<purple>"},
// {"Hyrule Castle", "<silver>"}
};
if (requiredDungeonText.Empty()) {
requiredDungeonText += getTextObject("No Required Dungeons Text");
for (const auto& world : worlds) {
auto& requiredDungeonText = world->AddNewText("Links House Sign");
for (const auto& [dungeonName, dungeon] : world->GetDungeonTable()) {
if (dungeon->IsRequired()) {
requiredDungeonText += dungeonColors.at(dungeonName) + getTextObject(dungeonName) + "\n";
}
}
if (requiredDungeonText.Empty()) {
requiredDungeonText += getTextObject("No Required Dungeons Text");
}
}
}
static void doItemTextReplacement(world::World* world,
static void doItemTextReplacement(const std::unique_ptr<world::World>& world,
const std::string& locationName,
const std::list<std::string>& textNames,
Text::Color color) {
@@ -47,44 +50,40 @@ namespace randomizer::logic::hints {
}
}
static void GenerateItemTextReplacements(world::World* world) {
doItemTextReplacement(world, "Fishing Hole Bottle", {"Fishing Hole Sign Text"}, Text::GREEN);
doItemTextReplacement(world, "Charlo Donation Blessing", {"Charlo Donation Ask Text"}, Text::GREEN);
doItemTextReplacement(world, "Sera Shop Slingshot", {"Slingshot Shop Text",
"Slingshot Shop Too Expensive Text", "Slingshot Shop Purchase Confirmation Text",
"Slingshot Shop After Purchase Text"}, Text::ORANGE);
doItemTextReplacement(world, "Barnes Bomb Bag", {"Barnes Special Offer Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Wooden Shield", {"Kakariko Malo Mart Wooden Shield Purchase Confirmation Text",
"Kakariko Malo Mart Wooden Shield Too Expensive Text", "Kakariko Malo Mart Wooden Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hylian Shield", {"Kakariko Malo Mart Hylian Shield Purchase Confirmation Text",
"Kakariko Malo Mart Hylian Shield Too Expensive Text", "Kakariko Malo Mart Hylian Shield After Purchase Text",
"Kakariko Malo Mart Hylian Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Red Potion", {"Kakariko Malo Mart Red Potion Too Expensive Text",
"Kakariko Malo Mart Red Potion Purchase Confirmation Text", "Kakariko Malo Mart Red Potion Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hawkeye", {"Kakariko Malo Mart Hawkeye Purchase Confirmation Text",
"Kakariko Malo Mart Hawkeye Too Expensive Text", "Kakariko Malo Mart Hawkeye After Purchase Text",
"Kakariko Malo Mart Hawkeye Coming Soon Text", "Kakariko Malo Mart Hawkeye Text"}, Text::ORANGE);
doItemTextReplacement(world, "Castle Town Malo Mart Magic Armor", {"Chudleys Shop Magic Armor Text",
"Castle Town Malo Mart Magic Armor After Purchase Text", "Castle Town Malo Mart Magic Armor Text",
"Castle Town Malo Mart Magic Armor Sold Out Text"}, Text::ORANGE);
doItemTextReplacement(world, "Coro Bottle", {"Coro Bottle Offer 1 Text",
"Coro Bottle Offer 2 Text", "Coro Bottle Offer 3 Text", "Coro Bottle Offer 4 Text"}, Text::ORANGE);
}
void GenerateAllHints(world::WorldPool& worlds) {
static void GenerateItemTextReplacements(world::WorldPool& worlds) {
for (const auto& world : worlds) {
GenerateAllHints(world);
doItemTextReplacement(world, "Fishing Hole Bottle", {"Fishing Hole Sign Text"}, Text::GREEN);
doItemTextReplacement(world, "Charlo Donation Blessing", {"Charlo Donation Ask Text"}, Text::GREEN);
doItemTextReplacement(world, "Sera Shop Slingshot", {"Slingshot Shop Text",
"Slingshot Shop Too Expensive Text", "Slingshot Shop Purchase Confirmation Text",
"Slingshot Shop After Purchase Text"}, Text::ORANGE);
doItemTextReplacement(world, "Barnes Bomb Bag", {"Barnes Special Offer Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Wooden Shield", {"Kakariko Malo Mart Wooden Shield Purchase Confirmation Text",
"Kakariko Malo Mart Wooden Shield Too Expensive Text", "Kakariko Malo Mart Wooden Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hylian Shield", {"Kakariko Malo Mart Hylian Shield Purchase Confirmation Text",
"Kakariko Malo Mart Hylian Shield Too Expensive Text", "Kakariko Malo Mart Hylian Shield After Purchase Text",
"Kakariko Malo Mart Hylian Shield Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Red Potion", {"Kakariko Malo Mart Red Potion Too Expensive Text",
"Kakariko Malo Mart Red Potion Purchase Confirmation Text", "Kakariko Malo Mart Red Potion Text"}, Text::ORANGE);
doItemTextReplacement(world, "Kakariko Village Malo Mart Hawkeye", {"Kakariko Malo Mart Hawkeye Purchase Confirmation Text",
"Kakariko Malo Mart Hawkeye Too Expensive Text", "Kakariko Malo Mart Hawkeye After Purchase Text",
"Kakariko Malo Mart Hawkeye Coming Soon Text", "Kakariko Malo Mart Hawkeye Text"}, Text::ORANGE);
doItemTextReplacement(world, "Castle Town Malo Mart Magic Armor", {"Chudleys Shop Magic Armor Text",
"Castle Town Malo Mart Magic Armor After Purchase Text", "Castle Town Malo Mart Magic Armor Text",
"Castle Town Malo Mart Magic Armor Sold Out Text"}, Text::ORANGE);
doItemTextReplacement(world, "Coro Bottle", {"Coro Bottle Offer 1 Text",
"Coro Bottle Offer 2 Text", "Coro Bottle Offer 3 Text", "Coro Bottle Offer 4 Text"}, Text::ORANGE);
}
}
void GenerateAllHints(const std::unique_ptr<world::World>& world) {
GenerateRequiredDungeonsHint(world.get());
GenerateItemTextReplacements(world.get());
void GenerateAllHints(world::WorldPool& worlds) {
GenerateRequiredDungeonsHint(worlds);
GenerateItemTextReplacements(worlds);
}
}
@@ -3,6 +3,5 @@
namespace randomizer::logic::hints {
void GenerateAllHints(world::WorldPool& worldPool);
void GenerateAllHints(const std::unique_ptr<world::World>& world);
}
@@ -842,7 +842,7 @@ namespace randomizer::logic::world
// Disable the dungeon's starting entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisabled(true);
entrance->SetDisbled(true);
}
// Run an accessibility search to see which locations inherently require accessing this dungeon
@@ -864,7 +864,7 @@ namespace randomizer::logic::world
// Re-enable the dungeon's entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisabled(false);
entrance->SetDisbled(false);
}
}
}
@@ -916,7 +916,7 @@ namespace randomizer::logic::world
// Disable the dungeon's starting entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisabled(true);
entrance->SetDisbled(true);
}
// Check if the game is beatable, set dungeon as required if so. If the dungeon is not required and barren
@@ -942,7 +942,7 @@ namespace randomizer::logic::world
// Re-enable the dungeon's entrances
for (auto& entrance : dungeon->GetStartingEntrances())
{
entrance->SetDisabled(false);
entrance->SetDisbled(false);
}
}
}
@@ -1169,13 +1169,11 @@ namespace randomizer::logic::world
return this->_startingItemPool;
}
location::Location* World::GetLocation(const std::string& name, const bool& ignoreError)
location::Location* World::GetLocation(const std::string& name)
{
if (!this->_locationTable.contains(name))
{
if (!ignoreError)
throw std::runtime_error("Unknown location name \"" + name + "\"");
return nullptr;
throw std::runtime_error("Unknown location name \"" + name + "\"");
}
return this->_locationTable.at(name).get();
}
@@ -131,7 +131,7 @@ namespace randomizer::logic::world
item::Item* GetGameWinningItem() const;
item_pool::ItemPool& GetItemPool();
item_pool::ItemPool& GetStartingItemPool();
location::Location* GetLocation(const std::string& name, const bool& ignoreError = false);
location::Location* GetLocation(const std::string& name);
location::LocationPool GetAllLocations(const bool& includeNonItemLocations = false);
area::Area* GetArea(const std::string& name, const bool& createIfNotFound = false);
area::Area* GetRootArea() const;
+9 -15
View File
@@ -15,7 +15,6 @@
#include <iostream>
#include "dusk/logging.h"
#include "dusk/archipelago/archipelago_context.hpp"
#include "dusk/ui/rando_config.hpp"
#include "dusk/randomizer/game/randomizer_context.hpp"
@@ -46,24 +45,19 @@ namespace randomizer
return std::nullopt;
}
void Randomizer::GenerateTrackerWorld(bool useAntiSpoilerLog) {
void Randomizer::GenerateTrackerWorld() {
auto contextHash = randomizer_GetContext().mHash;
if (!useAntiSpoilerLog) {
this->_config.LoadFromFile(GetConfigPath(), GetPrefPath());
this->_config.SetHash(contextHash);
}else {
if (contextHash.empty()) {
return;
}
std::filesystem::path seedSettings = dusk::ui::GetRandomizerSeedsPath() /
contextHash / (contextHash + " Anti-Spoiler Log.txt");
this->_config.LoadFromFile(seedSettings, GetPrefPath());
this->_config.SetHash(contextHash);
if (contextHash.empty()) {
return;
}
std::filesystem::path seedSettings = dusk::ui::GetRandomizerSeedsPath() /
contextHash / (contextHash + " Anti-Spoiler Log.txt");
this->_config.LoadFromFile(seedSettings, GetPrefPath());
this->_config.SetHash(contextHash);
std::unique_ptr<logic::world::World> world = std::make_unique<logic::world::World>(1, this);
world->SetSettings(this->_config.GetSettingsList().front());
// Always use logic when building a tracker world
+1 -1
View File
@@ -19,7 +19,7 @@ namespace randomizer
*/
std::optional<std::string> Generate();
void GenerateWorlds();
void GenerateTrackerWorld(bool useAntiSpoilerLog = true);
void GenerateTrackerWorld();
auto& GetConfig() { return this->_config; }
auto& GetWorlds() { return this->_worlds; }
+3 -10
View File
@@ -53,19 +53,12 @@ FetchContent_Declare(
GIT_TAG fdbae3f
)
message(STATUS "randomizer: Fetching APCpp")
FetchContent_Declare(
APCpp
GIT_REPOSITORY https://github.com/CraftyBoss/APCpp.git
GIT_TAG 2d92f75
)
FetchContent_MakeAvailable(yaml-cpp base64pp battery-embed APCpp)
FetchContent_MakeAvailable(yaml-cpp base64pp battery-embed)
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
set(GAME_COMPILE_DEFS ${GAME_COMPILE_DEFS} SOURCE_PATH_SIZE=${SOURCE_PATH_SIZE})
set(GAME_LIBS ${GAME_LIBS} yaml-cpp::yaml-cpp base64pp APCpp)
set(GAME_LIBS ${GAME_LIBS} yaml-cpp::yaml-cpp base64pp)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/randomizer")
# Put data files together for easier manipulation
# file(COPY "${CMAKE_SOURCE_DIR}/src/dusk/randomizer/data/" DESTINATION "${CMAKE_BINARY_DIR}/randomizer/data/" REGEX "^.*example.*$" EXCLUDE) # World, macros, and location info
# file(COPY "${CMAKE_SOURCE_DIR}/src/dusk/randomizer/data/" DESTINATION "${CMAKE_BINARY_DIR}/randomizer/data/" REGEX "^.*example.*$" EXCLUDE) # World, macros, and location info
+6 -30
View File
@@ -212,24 +212,6 @@ UserSettings g_userSettings = {
ConfigVar<std::string>{"randomizer.file2SeedHash", ""},
ConfigVar<std::string>{"randomizer.file3SeedHash", ""},
},
.archipelago = {
{
ConfigVar<std::string>{"archipelago.serverIP_file1", "archipelago.gg"},
ConfigVar<std::string>{"archipelago.serverIP_file2", "archipelago.gg"},
ConfigVar<std::string>{"archipelago.serverIP_file3", "archipelago.gg"}
},
{
ConfigVar<std::string>{"archipelago.serverPass_file1", ""},
ConfigVar<std::string>{"archipelago.serverPass_file2", ""},
ConfigVar<std::string>{"archipelago.serverPass_file3", ""}
},
{
ConfigVar<std::string>{"archipelago.slotName_file1", ""},
ConfigVar<std::string>{"archipelago.slotName_file2", ""},
ConfigVar<std::string>{"archipelago.slotName_file3", ""}
}
},
.cosmetics = {
.herosTunicCapColor = {"cosmetics.hatColor", ""},
@@ -248,6 +230,9 @@ UserSettings g_userSettings = {
.boomerangColor = {"cosmetics.boomerangColor", ""},
.ironBootsColor = {"cosmetics.ironBootsColor", ""},
.spinnerColor = {"cosmetics.spinnerColor", ""},
.midnaHairBaseColor = {"cosmetics.midnaHairBaseColor", "Default"},
.midnaHairTipsColor = {"cosmetics.midnaHairTipsColor", "Default"},
.midnaChargeRingColor = {"cosmetics.midnaChargeRingColor", ""},
.linkHairColor = {"cosmetics.linkHairColor", ""},
.wolfLinkColor = {"cosmetics.wolfLinkColor", ""},
.eponaColor = {"cosmetics.eponaColor", ""},
@@ -425,18 +410,6 @@ void registerSettings() {
Register(g_userSettings.randomizer.seedHashes[0]);
Register(g_userSettings.randomizer.seedHashes[1]);
Register(g_userSettings.randomizer.seedHashes[2]);
Register(g_userSettings.archipelago.savesServerIP[0]);
Register(g_userSettings.archipelago.savesServerIP[1]);
Register(g_userSettings.archipelago.savesServerIP[2]);
Register(g_userSettings.archipelago.savesServerPass[0]);
Register(g_userSettings.archipelago.savesServerPass[1]);
Register(g_userSettings.archipelago.savesServerPass[2]);
Register(g_userSettings.archipelago.savesSlotName[0]);
Register(g_userSettings.archipelago.savesSlotName[1]);
Register(g_userSettings.archipelago.savesSlotName[2]);
Register(g_userSettings.cosmetics.herosTunicCapColor);
Register(g_userSettings.cosmetics.herosTunicTorsoColor);
@@ -454,6 +427,9 @@ void registerSettings() {
Register(g_userSettings.cosmetics.boomerangColor);
Register(g_userSettings.cosmetics.ironBootsColor);
Register(g_userSettings.cosmetics.spinnerColor);
Register(g_userSettings.cosmetics.midnaHairBaseColor);
Register(g_userSettings.cosmetics.midnaHairTipsColor);
Register(g_userSettings.cosmetics.midnaChargeRingColor);
Register(g_userSettings.cosmetics.linkHairColor);
Register(g_userSettings.cosmetics.wolfLinkColor);
Register(g_userSettings.cosmetics.eponaColor);
-168
View File
@@ -1,168 +0,0 @@
#include <dusk/ui/archi_connect_modal.hpp>
#include <dusk/ui/string_button.hpp>
#include <thread>
#include "dusk/archipelago/archipelago_context.hpp"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui {
static std::atomic connectStatus = ArchiConnectModal::ConnectionStatus::None;
void CreateSetupConnectionInfoModal();
ArchiConnectModal::ArchiConnectModal() :
Modal({
.title = "Archipelago",
.bodyRml = "Connecting to Server...",
.onDismiss = [this](Modal& modal) {
mDoAud_seStartMenu(kSoundWindowClose);
modal.pop(false);
},
.icon = "verifying",
}) {
mRoot->SetProperty("white-space", "pre-line");
}
void ArchiConnectModal::update() {
Modal::update();
auto currentStatus = connectStatus.load();
if (currentStatus != mDisplayedStatus) {
mDisplayedStatus = currentStatus;
if (currentStatus == ConnectionStatus::Success ||
currentStatus == ConnectionStatus::Error)
{
if (currentStatus == ConnectionStatus::Success) {
mDoAud_seStartMenu(kSoundSeedGenerateSuccess);
set_icon("celebration");
set_body("Sucessfully Connected to server!");
} else {
mDoAud_seStartMenu(kSoundSeedGenerateError);
set_icon("error");
set_body("Failed to Connect to server.");
}
add_action({
.label = "OK",
.onPressed = [currentStatus](Modal& modal) {
mDoAud_seStartMenu(kSoundWindowClose);
modal.pop(false);
// show connection setup modal on failure
if (currentStatus == ConnectionStatus::Error) {
CreateSetupConnectionInfoModal();
}
}
});
// Refocus so that we focus the new button
focus();
mDisplayedStatus = ConnectionStatus::Ready;
}else if (currentStatus == ConnectionStatus::Generating) {
set_body("Loading seed data into game...");
}else if (currentStatus == ConnectionStatus::Disconnecting) {
set_body("Disconnecting from previous server...");
}
connectStatus.store(mDisplayedStatus);
}
}
void HandleArchipelagoConnect() {
// if a connection was already established, disconnect before attempting a new connection.
if (archi::ArchipelagoContext::IsConnected()) {
connectStatus.store(ArchiConnectModal::ConnectionStatus::Disconnecting);
archi::ArchipelagoContext::DisconnectFromServer();
}
if (!archi::ArchipelagoContext::ConnectToServer(dComIfGs_getDataNum(), true)) {
archi::ArchipelagoContext::DisconnectFromServer();
connectStatus.store(ArchiConnectModal::ConnectionStatus::Error);
return;
}
connectStatus.store(ArchiConnectModal::ConnectionStatus::Generating);
while (!archi::ArchipelagoContext::IsReceivedLocationScouts()) {
std::this_thread::yield();
}
archi::ArchipelagoContext::GenerateLocalWorldData();
connectStatus.store(ArchiConnectModal::ConnectionStatus::Success);
}
void ConnectAndLoadArchipelago() {
connectStatus.store(ArchiConnectModal::ConnectionStatus::Connecting);
std::thread archiConnectThread(HandleArchipelagoConnect);
archiConnectThread.detach();
push_document(std::make_unique<ArchiConnectModal>());
if (auto* doc = top_document()) {
doc->focus();
}
}
void CreateSetupConnectionInfoModal() {
auto& doc = push_document(std::make_unique<MultiTextInputModal>(Modal::Props{
.title = "Connection Info",
.bodyRml = "",
.actions = {
ModalAction{
.label = "Connect",
.onPressed = [](Modal& modal) {
auto textModal = dynamic_cast<MultiTextInputModal*>(&modal);
int dataNum = dComIfGs_getDataNum();
archi::ArchipelagoContext::SetServerIp(textModal->get_input_text(0), dataNum);
archi::ArchipelagoContext::SetPassword(textModal->get_input_text(1), dataNum);
archi::ArchipelagoContext::SetSlotName(textModal->get_input_text(2), dataNum);
modal.pop(false);
ConnectAndLoadArchipelago();
},
},
ModalAction{
.label = "Cancel",
.onPressed = [](Modal& modal) {
// TODO: cancelling this modal will still progress file select to starting the save,
// which could potentially allow a user to break their save file.
modal.pop(false);
},
},
},
.icon = "information"
}));
auto inputModal = dynamic_cast<MultiTextInputModal*>(&doc);
int dataNum = dComIfGs_getDataNum();
inputModal->add_input_text("Server IP", archi::ArchipelagoContext::GetServerIp(dataNum));
inputModal->add_input_text("Password", archi::ArchipelagoContext::GetPassword(dataNum));
inputModal->add_input_text("Slot Name", archi::ArchipelagoContext::GetSlotName(dataNum));
}
void BeginArchipelagoConnectionUI(bool forceChangeConnection) {
if (forceChangeConnection) {
CreateSetupConnectionInfoModal();
return;
}
int dataNum = dComIfGs_getDataNum();
bool hasConnectInfo = (!archi::ArchipelagoContext::GetServerIp(dataNum).empty() && !archi::ArchipelagoContext::GetSlotName(dataNum).empty());
if (hasConnectInfo) {
ConnectAndLoadArchipelago();
}else {
CreateSetupConnectionInfoModal();
}
}
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "modal.hpp"
namespace dusk::ui
{
class ArchiConnectModal : public Modal {
public:
enum class ConnectionStatus {
None,
Ready,
Disconnecting,
Connecting,
Generating,
Success,
Error,
};
explicit ArchiConnectModal();
void update() override;
private:
ConnectionStatus mDisplayedStatus = ConnectionStatus::None;
};
void BeginArchipelagoConnectionUI(bool forceChangeConnection = false);
}
+74 -37
View File
@@ -1,6 +1,7 @@
#include "cosmetics.hpp"
#include "dusk/config.hpp"
#include "dusk/cosmetics/midna_hair_color.hpp"
#include "dusk/randomizer/generator/utility/string.hpp"
#include "pane.hpp"
#include "string_button.hpp"
@@ -45,7 +46,32 @@ static const auto masterSwordColors = std::unordered_map<std::string, std::strin
{"30d0d0", "Cyan"},
});
void add_cosmetic_option(Pane& leftPane, Pane& rightPane, const char* key, ConfigVar<std::string>& option,
// No custom hex colors for now
static const auto midnaHairColors = std::unordered_map<std::string, std::string>({
{"Default", "Default"},
{"Pink", "Pink"},
{"Red", "Red"},
{"Yellow", "Yellow"},
{"Green", "Green"},
{"Blue", "Blue"},
{"Purple", "Purple"},
{"Brown", "Brown"},
{"White", "White"},
});
static const auto chargeRingColors = std::unordered_map<std::string, std::string>({
{"ff9f9f", "Pink"},
{"ff0000", "Red"},
{"ffff00", "Yellow"},
{"00ff00", "Green"},
{"0000ff", "Blue"},
{"ff00ff", "Purple"},
{"331900", "Brown"},
{"feffff", "White"},
{"000000", "Black"},
});
void add_cosmetic_option(Pane& leftPane, Pane& rightPane, const std::string& key, ConfigVar<std::string>& option,
const std::unordered_map<std::string, std::string>& colorPresets = defaultHexColors) {
leftPane.register_control(leftPane.add_select_button({
.key = key,
@@ -64,42 +90,47 @@ void add_cosmetic_option(Pane& leftPane, Pane& rightPane, const char* key, Confi
pane.clear();
pane.add_rml(fmt::format("Choose {}. Leave blank for default value. A reload or reboot may be required to see color changes ingame.", key));
pane.add_child<StringButton>(StringButton::Props{
.key = "Edit Hex Color",
.getValue = [&option] {
return option;
},
.setValue = [&option](Rml::String str) {
// Make lowercase
for (char& c : str) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (!key.starts_with("Midna's Hair")) {
pane.add_child<StringButton>(StringButton::Props{
.key = "Edit Hex Color",
.getValue = [&option] {
return option;
},
.setValue = [&option](Rml::String str) {
// Make lowercase
for (char& c : str) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
option.setValue(str);
config::Save();
},
.maxLength = 6,
});
pane.add_button(ControlledButton::Props{
.text = "Default",
.isSelected = [&option] {
return option.getValue().empty();
}
}).on_pressed([key, &option] {
option.setValue("");
if (key.starts_with("Midna's Hair")) {
cosmetics::set_all_midna_hair_colors();
}
option.setValue(str);
config::Save();
},
.maxLength = 6,
});
});
pane.add_button(ControlledButton::Props{
.text = "Default",
.isSelected = [&option] {
return option.getValue().empty();
}
}).on_pressed([&option] {
option.setValue("");
config::Save();
});
pane.add_button(ControlledButton::Props{
.text = "Random Color",
}).on_pressed([&option] {
std::random_device rd{};
std::uniform_int_distribution dist(0, 0xFFFFFF);
std::string hexStr = randomizer::utility::str::intToHex(dist(rd), false);
option.setValue(hexStr);
config::Save();
});
pane.add_button(ControlledButton::Props{
.text = "Random Color",
}).on_pressed([&option] {
std::random_device rd{};
std::uniform_int_distribution dist(0, 0xFFFFFF);
std::string hexStr = randomizer::utility::str::intToHex(dist(rd), 6, false);
option.setValue(hexStr);
config::Save();
});
}
for (const auto& [hexStr, color] : colorPresets) {
pane.add_button(ControlledButton::Props{
@@ -107,8 +138,11 @@ void add_cosmetic_option(Pane& leftPane, Pane& rightPane, const char* key, Confi
.isSelected = [hexStr, &option] {
return option.getValue() == hexStr;
},
}).on_pressed([hexStr, &option] {
}).on_pressed([key, hexStr, &option] {
option.setValue(hexStr);
if (key.starts_with("Midna's Hair")) {
cosmetics::set_all_midna_hair_colors();
}
config::Save();
});
}
@@ -142,13 +176,16 @@ CosmeticsWindow::CosmeticsWindow() {
});
add_tab("Misc. Colors", [this, &cosmetics](Rml::Element* content) {
add_tab("Misc Colors", [this, &cosmetics](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Controlled);
add_cosmetic_option(leftPane, rightPane, "Midna's Hair Base Color", cosmetics.midnaHairBaseColor, midnaHairColors);
add_cosmetic_option(leftPane, rightPane, "Midna's Hair Tips Color", cosmetics.midnaHairTipsColor, midnaHairColors);
add_cosmetic_option(leftPane, rightPane, "Midna Charge Ring Color", cosmetics.midnaChargeRingColor, chargeRingColors);
add_cosmetic_option(leftPane, rightPane, "Link's Hair Color", cosmetics.linkHairColor);
add_cosmetic_option(leftPane, rightPane, "Wolf Link Color", cosmetics.wolfLinkColor);
add_cosmetic_option(leftPane, rightPane, "Epona Color", cosmetics.eponaColor);
});
}
}
}
-63
View File
@@ -133,67 +133,4 @@ void TextInputModal::update() {
Document::update();
}
MultiTextInputModal::MultiTextInputModal(Props props) : Modal(props) {}
void MultiTextInputModal::add_input_text(const Rml::String& label, const Rml::String& startValue) {
auto modalBody = mDialog->QuerySelector(".modal-body");
bool isFirst = mInputs.empty();
mInputs.push_back(InputEntry {});
auto& entry = mInputs.back();
int idx = mInputs.size() - 1;
entry.text = startValue;
entry.button = std::make_unique<StringButton>(modalBody, StringButton::Props{
.key = label,
.getValue = [idx, this]{return mInputs[idx].text;},
.setValue = [idx, this](Rml::String value) { mInputs[idx].text = value; },
});
if (isFirst)
entry.button->start_editing();
}
bool MultiTextInputModal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
auto retVal = Modal::handle_nav_command(event, cmd);
if (!retVal) {
if (cmd == NavCommand::Down) {
if (mTextSelIdx < mInputs.size()) {
mTextSelIdx++;
if (mTextSelIdx >= mInputs.size()) {
mButtons[0]->focus();
}else {
mInputs[mTextSelIdx].button->focus();
}
}else {
mTextSelIdx = 0;
mInputs[mTextSelIdx].button->focus();
}
mDoAud_seStartMenu(kSoundItemFocus);
retVal = true;
}else if (cmd == NavCommand::Up && mTextSelIdx >= 0) {
mTextSelIdx--;
if (mTextSelIdx < 0) {
mTextSelIdx = mInputs.size();
mButtons[0]->focus();
}else {
mInputs[mTextSelIdx].button->focus();
}
mDoAud_seStartMenu(kSoundItemFocus);
retVal = true;
}
}
return retVal;
}
void MultiTextInputModal::update() {
for (const auto& input : mInputs) {
input.button->update();
}
Modal::update();
}
} // namespace dusk::ui
-23
View File
@@ -53,27 +53,4 @@ private:
Rml::String mInputText{};
};
class MultiTextInputModal : public Modal {
public:
explicit MultiTextInputModal(Props props);
void add_input_text(const Rml::String& label, const Rml::String& startValue = "");
Rml::String get_input_text(int idx) {return mInputs[idx].text; }
void update() override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
struct InputEntry {
std::unique_ptr<StringButton> button;
Rml::String text;
};
std::vector<InputEntry> mInputs{};
int mTextSelIdx = 0;
};
} // namespace dusk::ui
+1
View File
@@ -137,6 +137,7 @@ SelectButton& rando_config_button(
newIndex = 0;
}
setting->SetCurrentOption(newIndex);
SaveRandomizerConfig();
updateRightPane();
};
+5 -2
View File
@@ -49,6 +49,7 @@
#include "SSystem/SComponent/c_API.h"
#include "dusk/android_frame_rate.hpp"
#include "dusk/app_info.hpp"
#include "dusk/cosmetics/midna_hair_color.hpp"
#include "dusk/crash_handler.h"
#include "dusk/crash_reporting.h"
#include "dusk/data.hpp"
@@ -255,12 +256,12 @@ void main01(void) {
goto eventsDone;
case AURORA_PAUSED:
dusk::audio::SetPaused(true);
dusk::mouse::onFocusLost();
dusk::mouse::on_focus_lost();
break;
case AURORA_UNPAUSED:
dusk::audio::SetPaused(false);
dusk::game_clock::reset_frame_timer();
dusk::mouse::onFocusGained();
dusk::mouse::on_focus_gained();
break;
case AURORA_SDL_EVENT:
dusk::mouse::handle_event(event->sdl);
@@ -665,6 +666,8 @@ int game_main(int argc, char* argv[]) {
dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);
dusk::audio::EnableHrtf = dusk::getSettings().audio.enableHrtf;
dusk::cosmetics::set_all_midna_hair_colors();
// Run ImGui UI loop if Aurora couldn't initialize a backend
if (auroraInfo.backend == BACKEND_NULL) {
launchUILoop();