mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-08 20:24:47 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9756612b35 | |||
| 49f27acb2b | |||
| d1ea637d22 | |||
| 42910ab2fd | |||
| f54892b2c2 | |||
| f32e069c4b |
+2
-5
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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 ¤tMidnaHairColors.l_normalColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_normalKColor() {
|
||||
return ¤tMidnaHairColors.l_normalKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_normalKColor2() {
|
||||
return ¤tMidnaHairColors.l_normalKColor2;
|
||||
}
|
||||
|
||||
const GXColorS10* get_midna_hair_bigColor() {
|
||||
return ¤tMidnaHairColors.l_bigColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_bigKColor() {
|
||||
return ¤tMidnaHairColors.l_bigKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lNormalKColor() {
|
||||
return ¤tMidnaHairColors.l_lNormalKColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lNormalKColor2() {
|
||||
return ¤tMidnaHairColors.l_lNormalKColor2;
|
||||
}
|
||||
|
||||
const GXColorS10* get_midna_hair_lBigColor() {
|
||||
return ¤tMidnaHairColors.l_lBigColor;
|
||||
}
|
||||
|
||||
const GXColor* get_midna_hair_lBigKColor2() {
|
||||
return ¤tMidnaHairColors.l_lBigKColor2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>());
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -137,6 +137,7 @@ SelectButton& rando_config_button(
|
||||
newIndex = 0;
|
||||
}
|
||||
setting->SetCurrentOption(newIndex);
|
||||
SaveRandomizerConfig();
|
||||
updateRightPane();
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user