Merge branch 'develop' of https://github.com/HarbourMasters/Shipwright into develop

This commit is contained in:
Kenix3 2022-07-14 01:35:52 -04:00
commit 16f52c03b9
287 changed files with 56540 additions and 4175 deletions

1
.gitignore vendored
View File

@ -405,3 +405,4 @@ tags
oot.otr
*.sav
shipofharkinian.ini
shipofharkinian.json

View File

@ -22,7 +22,7 @@
```bash
# Clone the repo
git clone https://github.com/HarbourMasters/Shipwright.git
cd ShipWright
cd Shipwright
# Copy the baserom to the OTRExporter folder
cp <path to your ROM> OTRExporter
# Build the docker image
@ -48,7 +48,7 @@ make -j $(nproc) OPTFLAGS=-O2 DEBUG=0
## macOS
1. Requires `gcc@12, sdl2, libpng, glew, dylibbundler` (can be installed via brew, etc)
1. Requires Xcode (or xcode-tools) && `sdl2, libpng, glew, dylibbundler` (can be installed via brew, etc)
```bash
# Clone the repo
git clone https://github.com/HarbourMasters/Shipwright.git
@ -59,13 +59,14 @@ cp <path to your ROM> OTRExporter
cd soh
# Extract the assets/Compile the exporter/Run the exporter
# -jX defines number of cores to use for compilation - lower or remove entirely if having issues
make setup -j8 DEBUG=0 CC=gcc-12 CXX=g++-12
make setup -j8 DEBUG=0
# Compile the code (watch the -j parameter as above)
make -j8 DEBUG=0 CC=gcc-12 CXX=g++-12
make -j8 DEBUG=0
# Create macOS app bundle
make filledappbundle
make appbundle
```
9. Launch soh app in the soh folder!
9. Copy your OTR file to ~/Library/Application\ Support/com.shipofharkinian.soh
10. Launch soh app in the soh folder!
# Compatible Roms
```

4
Jenkinsfile vendored
View File

@ -137,8 +137,8 @@ pipeline {
sh '''
cp ../../ZELOOTD.z64 OTRExporter/baserom_non_mq.z64
cd soh
make setup -j4 DEBUG=0 OPTFLAGS=-O2 CC=gcc-12 CXX=g++-12
make -j4 DEBUG=0 OPTFLAGS=-O2 CC=gcc-12 CXX=g++-12
export CC="clang -arch arm64 -arch x86_64"; export CXX="clang++ -arch arm64 -arch x86_64"; make setup -j4 OPTFLAGS=-O2 DEBUG=0 LD="ld"
export CC="clang -arch arm64 -arch x86_64"; export CXX="clang++ -arch arm64 -arch x86_64"; make -j4 DEBUG=0 OPTFLAGS=-O2 LD="ld"
make -j4 appbundle
mv ../README.md readme.txt
7z a soh-mac.7z soh.app readme.txt

View File

@ -96,14 +96,14 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
{
case G_NOOP:
{
Gfx value = gsDPNoOp();
Gfx value = {gsDPNoOp()};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_ENDDL:
{
Gfx value = gsSPEndDisplayList();
Gfx value = {gsSPEndDisplayList()};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -114,7 +114,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t nnnn = (data & 0x0000FFFF00000000ULL) >> 32;
int32_t vvvvvvvv = (data & 0x00000000FFFFFFFFULL);
Gfx value = gsSPModifyVertex(nnnn / 2, ww, vvvvvvvv);
Gfx value = {gsSPModifyVertex(nnnn / 2, ww, vvvvvvvv)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -131,35 +131,35 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t cccccc = (data & 0x00FFFFFF00000000) >> 32;
int32_t ssssssss = (data & 0xFFFFFFFF);
Gfx value = gsSPGeometryMode(~cccccc, ssssssss);
Gfx value = {gsSPGeometryMode(~cccccc, ssssssss)};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_RDPPIPESYNC:
{
Gfx value = gsDPPipeSync();
Gfx value = {gsDPPipeSync()};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_RDPLOADSYNC:
{
Gfx value = gsDPLoadSync();
Gfx value = {gsDPLoadSync()};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_RDPTILESYNC:
{
Gfx value = gsDPTileSync();
Gfx value = {gsDPTileSync()};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_RDPFULLSYNC:
{
Gfx value = gsDPFullSync();
Gfx value = {gsDPFullSync()};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -169,14 +169,14 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t hhhhhh = (data & 0x00FFFFFF00000000) >> 32;
int32_t llllllll = (data & 0x00000000FFFFFFFF);
Gfx value = gsDPSetOtherMode(hhhhhh, llllllll);
Gfx value = {gsDPSetOtherMode(hhhhhh, llllllll)};
word0 = value.words.w0;
word1 = value.words.w1;
}
break;
case G_POPMTX:
{
Gfx value = gsSPPopMatrix(data);
Gfx value = {gsSPPopMatrix(data)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -188,7 +188,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
uint8_t b = (uint8_t)((data & 0xFF00FF00) >> 8);
uint8_t a = (uint8_t)((data & 0x000000FF) >> 0);
Gfx value = gsDPSetEnvColor(r, g, b, a);
Gfx value = {gsDPSetEnvColor(r, g, b, a)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -204,7 +204,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
mm = (mm & 0x0FFFFFFF) + 1;
Gfx value = gsSPMatrix(mm, pp);
Gfx value = {gsSPMatrix(mm, pp)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -214,7 +214,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
uint32_t mm = (data & 0x00000000FFFFFFFF);
pp ^= G_MTX_PUSH;
Gfx value = gsSPMatrix(mm, pp);
Gfx value = {gsSPMatrix(mm, pp)};
word0 = value.words.w0;
word1 = value.words.w1;
@ -253,7 +253,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t xxx = (data & 0x0000000000FFF000) >> 12;
int32_t ddd = (data & 0x0000000000000FFF);
Gfx value = gsDPLoadBlock(i, sss, ttt, xxx, ddd);
Gfx value = {gsDPLoadBlock(i, sss, ttt, xxx, ddd)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -263,7 +263,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t vvvv = (data & 0xFFFF00000000) >> 32;
int32_t wwww = (data & 0x0000FFFF);
Gfx value = gsSPCullDisplayList(vvvv / 2, wwww / 2);
Gfx value = {gsSPCullDisplayList(vvvv / 2, wwww / 2)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -279,7 +279,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
uint32_t z = (data & 0x00000000FFFFFFFF) >> 0;
uint32_t h = (data & 0xFFFFFFFF);
Gfx value = gsSPBranchLessZraw3(h & 0x00FFFFFF);
Gfx value = {gsSPBranchLessZraw3(h & 0x00FFFFFF)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -292,7 +292,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
break;
case G_RDPHALF_2:
{
Gfx value = gsDPWordLo(data & 0xFFFFFFFF);
Gfx value = {gsDPWordLo(data & 0xFFFFFFFF)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -305,7 +305,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t XXX = (data & 0x0000000000FFF000) >> 12;
int32_t YYY = (data & 0x0000000000000FFF);
Gfx value = gsSPTextureRectangle2(XXX, YYY, xxx, yyy, i);
Gfx value = {gsSPTextureRectangle2(XXX, YYY, xxx, yyy, i)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -324,7 +324,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int bp = 0;
Gfx value = gsSPBranchLessZraw2(0xDEADABCD, (a / 5) | (b / 2), z);
Gfx value = {gsSPBranchLessZraw2(0xDEADABCD, (a / 5) | (b / 2), z)};
word0 = (value.words.w0 & 0x00FFFFFF) + (G_BRANCH_Z_OTR << 24);
word1 = value.words.w1;
@ -389,9 +389,9 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
u32 dListVal = (data & 0x0FFFFFFF) + 1;
if (pp != 0)
value = gsSPBranchList(dListVal);
value = {gsSPBranchList(dListVal)};
else
value = gsSPDisplayList(dListVal);
value = {gsSPDisplayList(dListVal)};
word0 = value.words.w0;
word1 = value.words.w1;
@ -469,7 +469,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t ddd = (____ & 0x700) >> 8;
int32_t nnnnnnn = (____ & 0xFE) >> 1;
Gfx value = gsSPTexture(ssss, tttt, lll, ddd, nnnnnnn);
Gfx value = {gsSPTexture(ssss, tttt, lll, ddd, nnnnnnn)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -480,7 +480,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t bb = ((data & 0x0000FF0000000000ULL) >> 40) / 2;
int32_t cc = ((data & 0x000000FF00000000ULL) >> 32) / 2;
Gfx test = gsSP1Triangle(aa, bb, cc, 0);
Gfx test = {gsSP1Triangle(aa, bb, cc, 0)};
word0 = test.words.w0;
word1 = test.words.w1;
}
@ -494,7 +494,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t ee = ((data & 0x0000000000FF00ULL) >> 8) / 2;
int32_t ff = ((data & 0x000000000000FFULL) >> 0) / 2;
Gfx test = gsSP2Triangles(aa, bb, cc, 0, dd, ee, ff, 0);
Gfx test = {gsSP2Triangles(aa, bb, cc, 0, dd, ee, ff, 0)};
word0 = test.words.w0;
word1 = test.words.w1;
}
@ -506,7 +506,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t cc = ((data & 0x000000FF00000000ULL) >> 32) / 2;
int32_t dd = ((data & 0x000000000000FFULL)) / 2;
Gfx test = gsSP1Quadrangle(aa, bb, cc, dd, 0);
Gfx test = {gsSP1Quadrangle(aa, bb, cc, dd, 0)};
word0 = test.words.w0;
word1 = test.words.w1;
}
@ -520,7 +520,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t bb = (data & 0x000000000000FF00) >> 8;
int32_t aa = (data & 0x00000000000000FF) >> 0;
Gfx value = gsDPSetPrimColor(mm, ff, rr, gg, bb, aa);
Gfx value = {gsDPSetPrimColor(mm, ff, rr, gg, bb, aa)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -534,7 +534,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
// TODO: Output the correct render modes in data
Gfx value = gsSPSetOtherMode(0xE2, sft, len, dd);
Gfx value = {gsSPSetOtherMode(0xE2, sft, len, dd)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -552,11 +552,11 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
if (sft == 14) // G_MDSFT_TEXTLUT
{
const char* types[] = { "G_TT_NONE", "G_TT_NONE", "G_TT_RGBA16", "G_TT_IA16" };
value = gsDPSetTextureLUT(dd >> 14);
value = {gsDPSetTextureLUT(dd >> 14)};
}
else
{
value = gsSPSetOtherMode(0xE3, sft, nn + 1, dd);
value = {gsSPSetOtherMode(0xE3, sft, nn + 1, dd)};
}
word0 = value.words.w0;
@ -583,7 +583,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t bbbb = (data & 0b0000000000000000000000000000000000000000000000000000000011110000) >> 4;
int32_t uuuu = (data & 0b0000000000000000000000000000000000000000000000000000000000001111);
Gfx value = gsDPSetTile(fff, ii, nnnnnnnnn, mmmmmmmmm, ttt, pppp, cc, aaaa, ssss, dd, bbbb, uuuu);
Gfx value = {gsDPSetTile(fff, ii, nnnnnnnnn, mmmmmmmmm, ttt, pppp, cc, aaaa, ssss, dd, bbbb, uuuu)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -607,7 +607,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t ab1 = (data & 0b00000000000000000000000000000000000000000000000000000000111000) >> 3;
int32_t ad1 = (data & 0b00000000000000000000000000000000000000000000000000000000000111) >> 0;
Gfx value = gsDPSetCombineLERP2(a0, b0, c0, d0, aa0, ab0, ac0, ad0, a1, b1, c1, d1, aa1, ab1, ac1, ad1);
Gfx value = {gsDPSetCombineLERP2(a0, b0, c0, d0, aa0, ab0, ac0, ad0, a1, b1, c1, d1, aa1, ab1, ac1, ad1)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -620,7 +620,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t vvv = (data & 0x0000000000000FFF);
int32_t i = (data & 0x000000000F000000) >> 24;
Gfx value = gsDPSetTileSize(i, sss, ttt, uuu, vvv);
Gfx value = {gsDPSetTileSize(i, sss, ttt, uuu, vvv)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -630,7 +630,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t t = (data & 0x0000000007000000) >> 24;
int32_t ccc = (data & 0x00000000003FF000) >> 14;
Gfx value = gsDPLoadTLUTCmd(t, ccc);
Gfx value = {gsDPLoadTLUTCmd(t, ccc)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -643,7 +643,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int uuu = (data & 0x0000000000FFF000) >> 12;
int vvv= (data & 0x0000000000000FFF);
Gfx value = gsDPLoadTile(i, sss, ttt, uuu, vvv);
Gfx value = {gsDPLoadTile(i, sss, ttt, uuu, vvv)};
word0 = value.words.w0;
word1 = value.words.w1;
}
@ -661,7 +661,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
uint32_t fmt = (__ & 0xE0) >> 5;
uint32_t siz = (__ & 0x18) >> 3;
Gfx value = gsDPSetTextureImage(fmt, siz, www + 1, (seg & 0x0FFFFFFF) + 1);
Gfx value = {gsDPSetTextureImage(fmt, siz, www + 1, (seg & 0x0FFFFFFF) + 1)};
word0 = value.words.w0;
word1 = value.words.w1;
@ -679,7 +679,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
uint32_t fmt = (__ & 0xE0) >> 5;
uint32_t siz = (__ & 0x18) >> 3;
Gfx value = gsDPSetTextureImage(fmt, siz, www + 1, __);
Gfx value = {gsDPSetTextureImage(fmt, siz, www + 1, __)};
word0 = value.words.w0 & 0x00FFFFFF;
word0 += (G_SETTIMG_OTR << 24);
//word1 = value.words.w1;
@ -722,7 +722,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
int32_t aa = (data & 0x000000FF00000000ULL) >> 32;
int32_t nn = (data & 0x000FF00000000000ULL) >> 44;
Gfx value = gsSPVertex(data & 0xFFFFFFFF, nn, ((aa >> 1) - nn));
Gfx value = {gsSPVertex(data & 0xFFFFFFFF, nn, ((aa >> 1) - nn))};
word0 = value.words.w0;
word1 = value.words.w1 | 1;
@ -745,7 +745,7 @@ void OTRExporter_DisplayList::Save(ZResource* res, const fs::path& outPath, Bina
{
uint32_t diff = segOffset - vtxDecl->address;
Gfx value = gsSPVertex(diff, nn, ((aa >> 1) - nn));
Gfx value = {gsSPVertex(diff, nn, ((aa >> 1) - nn))};
word0 = value.words.w0;
word0 &= 0x00FFFFFF;
@ -893,4 +893,4 @@ std::string OTRExporter_DisplayList::GetPrefix(ZResource* res)
prefix = "code";
return prefix;
}
}

View File

@ -5,7 +5,7 @@ std::map<Ship::ResourceType, uint32_t> resourceVersions;
void InitVersionInfo()
{
resourceVersions = {
resourceVersions = std::map<Ship::ResourceType, uint32_t> {
{ Ship::ResourceType::Animation, 0 },
{ Ship::ResourceType::Model, 0 },
{ Ship::ResourceType::Texture, 0 },
@ -24,4 +24,4 @@ void InitVersionInfo()
{ Ship::ResourceType::Text, 0 },
{ Ship::ResourceType::Blob, 0 },
};
}
}

View File

@ -128,5 +128,3 @@ Nightly builds of Ship of Harkinian are available [here](https://builds.shipofha
MicTheMicrophone | Gwonam / The King
Amphibibro | Link
AceHeart | Zelda
###### Lemons

View File

@ -49,7 +49,7 @@ LDFLAGS := -lm -ldl \
-L../StormLib/build -L../libultraship -lbz2 -pthread -lultraship -lstorm
ifeq ($(UNAME), Darwin)
LDFLAGS += $(shell pkg-config --libs glew libpng zlib) $(shell sdl2-config --libs) -framework OpenGL
LDFLAGS += $(shell pkg-config --libs glew libpng zlib) $(shell sdl2-config --libs) -framework OpenGL -framework Foundation
INC += $(shell pkg-config --cflags libpng)
else
LDFLAGS += -lpng -lGL -lGLEW -lX11 -lz -lSDL2 -lpulse
@ -124,7 +124,7 @@ lib/libgfxd/libgfxd.a:
.PHONY: StormLib
StormLib:
LDFLAGS="" cmake -B ../StormLib/build -S ../StormLib
LDFLAGS="" cmake --build ../StormLib/build
$(MAKE) -C ../StormLib/build
.PHONY: ExporterTest
ExporterTest:

View File

@ -461,8 +461,8 @@ char* OldMain(char* infilename)
else
{
char comprType[5] = {
CommChunk.compressionTypeH >> 8, CommChunk.compressionTypeH & 0xFF,
CommChunk.compressionTypeL >> 8, CommChunk.compressionTypeL & 0xFF, 0};
static_cast<char>(CommChunk.compressionTypeH >> 8), static_cast<char>(CommChunk.compressionTypeH & 0xFF),
static_cast<char>(CommChunk.compressionTypeL >> 8), static_cast<char>(CommChunk.compressionTypeL & 0xFF), 0};
fail_parse("file is of the wrong compression type [got %s (%08x)]", &comprType,
cType);
}
@ -603,8 +603,8 @@ char* OldMain(char* infilename)
{
s32 startPos = aloops[0].start, endPos = aloops[0].end;
const char* markerNames[2] = {"start", "end"};
Marker markers[2] = {{1, startPos >> 16, startPos & 0xffff},
{2, endPos >> 16, endPos & 0xffff}};
Marker markers[2] = {{1, static_cast<u16>(startPos >> 16), static_cast<u16>(startPos & 0xffff)},
{2, static_cast<u16>(endPos >> 16), static_cast<u16>(endPos & 0xffff)}};
write_header(ofile, "MARK", 2 + 2 * sizeof(Marker) + 1 + 5 + 1 + 3);
s16 numMarkers = bswap16(2);
fwrite(&numMarkers, sizeof(s16), 1, ofile);
@ -666,4 +666,4 @@ char* OldMain(char* infilename)
fclose(ifile);
fclose(ofile);
return 0;
}
}

View File

@ -800,9 +800,11 @@ void ZFile::GenerateSourceHeaderFiles()
{
OutputFormatter formatter;
formatter.Write("#pragma once\n");
std::set<std::string> nameSet;
for (ZResource* res : resources)
{
std::string resSrc = res->GetSourceOutputHeader("");
std::string resSrc = res->GetSourceOutputHeader("", &nameSet);
formatter.Write(resSrc);
if (resSrc != "")
@ -811,7 +813,7 @@ void ZFile::GenerateSourceHeaderFiles()
for (auto& sym : symbolResources)
{
formatter.Write(sym.second->GetSourceOutputHeader(""));
formatter.Write(sym.second->GetSourceOutputHeader("", &nameSet));
}
formatter.Write(ProcessExterns());

View File

@ -7,6 +7,7 @@
#include "WarningHandler.h"
#include "ZFile.h"
#include <Globals.h>
#include <set>
#include <ZDisplayList.h>
#include <ZArray.h>
@ -304,7 +305,7 @@ void ZResource::GetSourceOutputCode([[maybe_unused]] const std::string& prefix)
}
}
std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix)
std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix, std::set<std::string> *nameSet)
{
if (Globals::Instance->otrMode && genOTRDef)
{
@ -342,9 +343,20 @@ std::string ZResource::GetSourceOutputHeader([[maybe_unused]] const std::string&
prefix = "text";
if (prefix != "")
str += StringHelper::Sprintf("#define %s \"__OTR__%s/%s/%s\"", name.c_str(), prefix.c_str(), outName.c_str(), nameStr.c_str());
str += StringHelper::Sprintf("#define d%s \"__OTR__%s/%s/%s\"", name.c_str(), prefix.c_str(), outName.c_str(), nameStr.c_str());
else
str += StringHelper::Sprintf("#define %s \"__OTR__%s/%s\"", name.c_str(), outName.c_str(), nameStr.c_str());
str += StringHelper::Sprintf("#define d%s \"__OTR__%s/%s\"", name.c_str(), outName.c_str(), nameStr.c_str());
if (nameSet && nameSet->find(name) == nameSet->end()) {
#ifdef _WIN32
str += StringHelper::Sprintf("\nstatic const __declspec(align(2)) char %s[] = d%s;", name.c_str(), name.c_str());
#else
str += StringHelper::Sprintf("\nstatic const char %s[] __attribute__((aligned (2))) = d%s;", name.c_str(), name.c_str());
#endif
if (nameSet) {
nameSet->insert(name);
}
}
return str;
}

View File

@ -2,6 +2,7 @@
#include <cstdint>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <vector>
@ -116,7 +117,7 @@ public:
[[nodiscard]] virtual std::string GetDefaultName(const std::string& prefix) const;
virtual void GetSourceOutputCode(const std::string& prefix);
virtual std::string GetSourceOutputHeader(const std::string& prefix);
virtual std::string GetSourceOutputHeader(const std::string& prefix, std::set<std::string> *nameSet);
virtual void CalcHash();
/**
* Exports the resource to binary format

View File

@ -48,7 +48,7 @@ std::string ZString::GetBodySourceCode() const
return StringHelper::Sprintf("\t\"%s\"", strData.data());
}
std::string ZString::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix)
std::string ZString::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix, std::set<std::string> *nameSet)
{
return StringHelper::Sprintf("#define %s_macro \"%s\"", name.c_str(), strData.data());
}

View File

@ -13,7 +13,7 @@ public:
Declaration* DeclareVar(const std::string& prefix, const std::string& bodyStr) override;
std::string GetBodySourceCode() const override;
std::string GetSourceOutputHeader(const std::string& prefix) override;
std::string GetSourceOutputHeader(const std::string& prefix, std::set<std::string> *nameSet) override;
std::string GetSourceTypeName() const override;
ZResourceType GetResourceType() const override;

View File

@ -74,7 +74,7 @@ size_t ZSymbol::GetRawDataSize() const
return typeSize;
}
std::string ZSymbol::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix)
std::string ZSymbol::GetSourceOutputHeader([[maybe_unused]] const std::string& prefix, std::set<std::string> *nameSet)
{
if (isArray)
{

View File

@ -18,7 +18,7 @@ public:
Declaration* DeclareVar(const std::string& prefix, const std::string& bodyStr) override;
std::string GetSourceOutputHeader(const std::string& prefix) override;
std::string GetSourceOutputHeader(const std::string& prefix, std::set<std::string> *nameSet) override;
std::string GetSourceTypeName() const override;
ZResourceType GetResourceType() const override;

View File

@ -2,6 +2,7 @@
HERE="$(dirname "$(readlink -f "${0}")")"/../..
export PATH="$HERE"/bin:"$HERE"/usr/bin:"$PATH"
export LD_LIBRARY_PATH="$HERE"/usr/lib:"$LD_LIBRARY_PATH"
while [[ ! -e "$PWD"/oot.otr ]]; do
export ASSETDIR="$(mktemp -d /tmp/assets-XXXXX)"

View File

@ -2,7 +2,7 @@
CXX ?= g++
CC ?= gcc
AR := ar
AR := ar
FORMAT := clang-format-11
UNAME := $(shell uname)
@ -11,25 +11,53 @@ DEBUG ?= 1
OPTFLAGS ?= -O0
LTO ?= 0
# flag to save whether the compiler being used is clang or gcc by checking CXX --version
CXX_IS_CLANG ?= $(shell $(CXX) --version | grep -c clang)
ifeq ($(CXX_IS_CLANG),1)
MXX := $(CXX)
else
MXX ?= clang++
endif
WARN := -Wall -Wextra -Werror \
-Wno-unused-variable \
-Wno-unused-parameter \
-Wno-unused-function \
-Wno-parentheses \
-Wno-narrowing \
-Wno-error=stringop-overflow \
-Wno-missing-field-initializers \
-Wno-error=multichar
-Wno-error=multichar \
-Wno-unused-command-line-argument \
-Wno-delete-non-abstract-non-virtual-dtor \
-Wno-unused-private-field \
-Wno-deprecated-copy-with-user-provided-copy \
-Wno-deprecated-declarations \
-Wno-unknown-warning-option
CWARN :=
CXXWARN := -Wno-deprecated-enum-enum-conversion -Wno-error=maybe-uninitialized
CXXWARN := -Wno-deprecated-enum-enum-conversion -Wno-deprecated-copy
ifneq ($(CXX_IS_CLANG),1)
WARN += -Wno-error=stringop-overflow
CXXWARN += -Wno-error=maybe-uninitialized
endif
CXXFLAGS := $(WARN) $(CXXWARN) -std=c++20 -D_GNU_SOURCE -DENABLE_OPENGL -DSPDLOG_ACTIVE_LEVEL=0
CFLAGS := $(WARN) $(CWARN) -std=c99 -D_GNU_SOURCE -DENABLE_OPENGL -DSPDLOG_ACTIVE_LEVEL=0
CPPFLAGS := -MMD
MMFLAGS := -Wno-deprecated-declarations -ObjC++ -fobjc-weak -fobjc-arc
# if not using clang, ask clang to use gcc standard library
ifneq ($(CXX_IS_CLANG),1)
STD_ISYSTEM=$(shell ${CXX} -xc++ -E -v - < /dev/null 2>&1 | grep "> search starts here" -A2 | tail -n 2 | head -n 1)
CXX_ISYSTEM=$(shell ${CXX} -xc++ -E -v - < /dev/null 2>&1 | grep "> search starts here" -A2 | tail -n 2 | tail -n 1)
MMFLAGS += -stdlib++-isystem ${STD_ISYSTEM} -cxx-isystem ${CXX_ISYSTEM}
endif
ifeq ($(UNAME), Darwin) #APPLE
CPPFLAGS += $(shell pkg-config --cflags sdl2 glew) -framework OpenGL
CPPFLAGS += $(shell pkg-config --cflags sdl2 glew) -framework OpenGL -framework Foundation
endif
ifneq ($(DEBUG),0)
@ -54,6 +82,7 @@ CXX_FILES := \
$(shell find libultraship/Lib/Fast3D -name "*.cpp") \
$(shell find libultraship -maxdepth 1 -name "*.cpp") \
$(shell find libultraship/Lib/ImGui -maxdepth 1 -name "*.cpp") \
$(shell find libultraship/Lib/Mercury -maxdepth 1 -name "*.cpp") \
libultraship/Lib/ImGui/backends/imgui_impl_opengl3.cpp \
libultraship/Lib/ImGui/backends/imgui_impl_sdl.cpp \
libultraship/Lib/StrHash64.cpp \
@ -63,12 +92,19 @@ C_FILES := \
libultraship/mixer.c \
libultraship/Lib/stb/stb_impl.c
MM_FILES := \
libultraship/OSXFolderManager.mm
FMT_FILES := $(shell find libultraship/ -type f \( -name "*.cpp" -o -name "*.h" \) -a -not -path "libultraship/Lib/*")
O_FILES := \
$(CXX_FILES:%.cpp=build/%.o) \
$(C_FILES:%.c=build/%.o)
ifeq ($(UNAME), Darwin) #APPLE
O_FILES += $(MM_FILES:%.mm=build/%.o)
endif
D_FILES := $(O_FILES:%.o=%.d)
LIB := libultraship.a
@ -79,6 +115,7 @@ INC_DIRS := $(addprefix -I, \
libultraship/Lib/spdlog \
libultraship/Lib/spdlog/include \
libultraship/Lib/ImGui \
libultraship/Lib/Mercury \
libultraship \
../StormLib/src \
)
@ -102,6 +139,9 @@ build/%.o: %.cpp
build/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) $(OPTFLAGS) $(INC_DIRS) -c $< -o $@
build/%.o: %.mm
$(MXX) $(MMFLAGS) $(CXXFLAGS) $(OPTFLAGS) $(INC_DIRS) -c $< -o $@
$(LIB): $(O_FILES)
$(AR) rcs $@ $^

View File

@ -1,164 +0,0 @@
#include "ConfigFile.h"
#include "spdlog/spdlog.h"
#include "GlobalCtx2.h"
#include "Window.h"
#include "GameSettings.h"
namespace Ship {
ConfigFile::ConfigFile(std::shared_ptr<GlobalCtx2> Context, const std::string& Path) : Context(Context), Path(Path), File(Path.c_str()) {
if (Path.empty()) {
SPDLOG_ERROR("ConfigFile received an empty file name");
exit(EXIT_FAILURE);
}
if (!File.read(Val)) {
if (!CreateDefaultConfig()) {
SPDLOG_ERROR("Failed to create default configs");
exit(EXIT_FAILURE);
}
}
}
ConfigFile::~ConfigFile() {
if (!Save()) {
SPDLOG_ERROR("Failed to save configs!!!");
}
SPDLOG_INFO("destruct configfile");
}
mINI::INIMap<std::string>& ConfigFile::operator[](const std::string& Section) {
return Val[Section];
}
mINI::INIMap<std::string> ConfigFile::get(const std::string& Section) {
return Val.get(Section);
}
bool ConfigFile::has(const std::string& Section) {
return Val.has(Section);
}
bool ConfigFile::remove(const std::string& Section) {
return Val.remove(Section);
}
void ConfigFile::clear() {
Val.clear();
}
std::size_t ConfigFile::size() const {
return Val.size();
}
bool ConfigFile::Save() {
return File.write(Val);
}
bool ConfigFile::CreateDefaultConfig() {
(*this)["ARCHIVE"]["Main Archive"] = "oot.otr";
(*this)["ARCHIVE"]["Patches Directory"] = "";
(*this)["SAVE"]["Save Filename"] = "oot_save.sav";
(*this)["CONTROLLERS"]["CONTROLLER 1"] = "Auto";
(*this)["CONTROLLERS"]["CONTROLLER 2"] = "Unplugged";
(*this)["CONTROLLERS"]["CONTROLLER 3"] = "Unplugged";
(*this)["CONTROLLERS"]["CONTROLLER 4"] = "Unplugged";
(*this)["KEYBOARD SHORTCUTS"]["KEY_FULLSCREEN"] = std::to_string(0x044);
(*this)["KEYBOARD SHORTCUTS"]["KEY_CONSOLE"] = std::to_string(0x029);
(*this)["WINDOW"]["WINDOW WIDTH"] = std::to_string(640);
(*this)["WINDOW"]["WINDOW HEIGHT"] = std::to_string(480);
(*this)["WINDOW"]["FULLSCREEN WIDTH"] = std::to_string(1920);
(*this)["WINDOW"]["FULLSCREEN HEIGHT"] = std::to_string(1080);
(*this)["WINDOW"]["FULLSCREEN"] = std::to_string(false);
(*this)["WINDOW"]["GFX BACKEND"] = "";
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CRIGHT)] = std::to_string(0x14D);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CLEFT)] = std::to_string(0x14B);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CDOWN)] = std::to_string(0x150);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CUP)] = std::to_string(0x148);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_R)] = std::to_string(0x013);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_L)] = std::to_string(0x012);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DRIGHT)] = std::to_string(0x023);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DLEFT)] = std::to_string(0x021);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DDOWN)] = std::to_string(0x022);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DUP)] = std::to_string(0x014);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_START)] = std::to_string(0x039);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_Z)] = std::to_string(0x02C);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_B)] = std::to_string(0x02E);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_A)] = std::to_string(0x02D);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKRIGHT)] = std::to_string(0x020);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKLEFT)] = std::to_string(0x01E);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKDOWN)] = std::to_string(0x01F);
(*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKUP)] = std::to_string(0x011);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CRIGHT)] = std::to_string(0x14D);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CLEFT)] = std::to_string(0x14B);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CDOWN)] = std::to_string(0x150);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CUP)] = std::to_string(0x148);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_R)] = std::to_string(0x013);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_L)] = std::to_string(0x012);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DRIGHT)] = std::to_string(0x023);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DLEFT)] = std::to_string(0x021);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DDOWN)] = std::to_string(0x022);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DUP)] = std::to_string(0x014);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_START)] = std::to_string(0x039);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_Z)] = std::to_string(0x02C);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_B)] = std::to_string(0x02E);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_A)] = std::to_string(0x02D);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKRIGHT)] = std::to_string(0x020);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKLEFT)] = std::to_string(0x01E);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKDOWN)] = std::to_string(0x01F);
(*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKUP)] = std::to_string(0x011);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CRIGHT)] = std::to_string(0x14D);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CLEFT)] = std::to_string(0x14B);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CDOWN)] = std::to_string(0x150);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CUP)] = std::to_string(0x148);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_R)] = std::to_string(0x013);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_L)] = std::to_string(0x012);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DRIGHT)] = std::to_string(0x023);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DLEFT)] = std::to_string(0x021);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DDOWN)] = std::to_string(0x022);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DUP)] = std::to_string(0x014);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_START)] = std::to_string(0x039);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_Z)] = std::to_string(0x02C);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_B)] = std::to_string(0x02E);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_A)] = std::to_string(0x02D);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKRIGHT)] = std::to_string(0x020);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKLEFT)] = std::to_string(0x01E);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKDOWN)] = std::to_string(0x01F);
(*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKUP)] = std::to_string(0x011);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CRIGHT)] = std::to_string(0x14D);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CLEFT)] = std::to_string(0x14B);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CDOWN)] = std::to_string(0x150);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CUP)] = std::to_string(0x148);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_R)] = std::to_string(0x013);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_L)] = std::to_string(0x012);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DRIGHT)] = std::to_string(0x023);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DLEFT)] = std::to_string(0x021);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DDOWN)] = std::to_string(0x022);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DUP)] = std::to_string(0x014);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_START)] = std::to_string(0x039);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_Z)] = std::to_string(0x02C);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_B)] = std::to_string(0x02E);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_A)] = std::to_string(0x02D);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKRIGHT)] = std::to_string(0x020);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKLEFT)] = std::to_string(0x01E);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKDOWN)] = std::to_string(0x01F);
(*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKUP)] = std::to_string(0x011);
(*this)["ENHANCEMENT SETTINGS"]["TEXT_SPEED"] = "1";
(*this)["SDL CONTROLLER 1"]["GUID"] = "";
(*this)["SDL CONTROLLER 2"]["GUID"] = "";
(*this)["SDL CONTROLLER 3"]["GUID"] = "";
(*this)["SDL CONTROLLER 4"]["GUID"] = "";
return File.generate(Val);
}
}

View File

@ -1,42 +0,0 @@
#ifndef CONFIG_FILE_H
#define CONFIG_FILE_H
#pragma once
#include <string>
#include <memory>
#include "Lib/mINI/src/mini/ini.h"
#include "UltraController.h"
#include "LUSMacros.h"
namespace Ship {
class GlobalCtx2;
class ConfigFile {
public:
ConfigFile(std::shared_ptr<GlobalCtx2> Context, const std::string& Path);
~ConfigFile();
bool Save();
// Expose the ini values.
mINI::INIMap<std::string>& operator[](const std::string& Section);
mINI::INIMap<std::string> get(const std::string& Section);
bool has(const std::string& Section);
bool remove(const std::string& Section);
void clear();
std::size_t size() const;
std::shared_ptr<GlobalCtx2> GetContext() { return Context.lock(); }
protected:
bool CreateDefaultConfig();
private:
mINI::INIStructure Val;
std::weak_ptr<GlobalCtx2> Context;
std::string Path;
mINI::INIFile File;
};
}
#endif

View File

@ -28,7 +28,7 @@ static bool ClearCommand(const std::vector<std::string>&) {
std::string toLowerCase(std::string in) {
std::string cpy(in);
std::ranges::transform(cpy, cpy.begin(), [](unsigned char c) { return std::tolower(c); });
std::transform(cpy.begin(), cpy.end(), cpy.begin(), ::tolower);
return cpy;
}
@ -204,11 +204,11 @@ void Console::Draw() {
for (int i = 0; i < static_cast<int>(channel.size()); i++) {
ConsoleLine line = channel[i];
if(!this->filter.empty() && line.text.find(this->filter) == std::string::npos) continue;
if(this->level_filter != NULLSTR && line.priority != (std::ranges::find(priority_filters, this->level_filter) - priority_filters.begin()) - 1) continue;
if(this->level_filter != NULLSTR && line.priority != (std::find(priority_filters.begin(), priority_filters.end(), this->level_filter) - priority_filters.begin()) - 1) continue;
std::string id = line.text + "##" + std::to_string(i);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
const bool is_selected = (this->selectedId == i) || std::ranges::find(this->selectedEntries, i) != this->selectedEntries.end();
const bool is_selected = (this->selectedId == i) || std::find(this->selectedEntries.begin(), this->selectedEntries.end(), i) != this->selectedEntries.end();
ImGui::PushStyleColor(ImGuiCol_Text, this->priority_colors[line.priority]);
if (ImGui::Selectable(id.c_str(), is_selected)) {
if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_LeftCtrl)) && !is_selected)

View File

@ -0,0 +1,155 @@
#include "ControlDeck.h"
#include "Window.h"
#include "Controller.h"
#include "DisconnectedController.h"
#include "KeyboardController.h"
#include "SDLController.h"
#include <Utils/StringHelper.h>
uint8_t* controllerBits;
void Ship::ControlDeck::Init(uint8_t* bits) {
ScanPhysicalDevices();
controllerBits = bits;
}
void Ship::ControlDeck::ScanPhysicalDevices() {
virtualDevices.clear();
physicalDevices.clear();
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (SDL_IsGameController(i)) {
auto sdl = std::make_shared<SDLController>(i);
sdl->Open();
physicalDevices.push_back(sdl);
}
}
physicalDevices.push_back(std::make_shared<KeyboardController>());
physicalDevices.push_back(std::make_shared<DisconnectedController>());
for (const auto& device : physicalDevices) {
for (int i = 0; i < MAXCONTROLLERS; i++) {
device->CreateDefaultBinding(i);
}
}
for (int i = 0; i < MAXCONTROLLERS; i++) {
virtualDevices.push_back(i == 0 ? 0 : static_cast<int>(physicalDevices.size()) - 1);
}
LoadControllerSettings();
}
void Ship::ControlDeck::SetPhysicalDevice(int slot, int deviceSlot) {
const std::shared_ptr<Controller> backend = physicalDevices[deviceSlot];
virtualDevices[slot] = deviceSlot;
*controllerBits |= (backend->Connected()) << slot;
}
void Ship::ControlDeck::WriteToPad(OSContPad* pad) const {
for (size_t i = 0; i < virtualDevices.size(); i++) {
physicalDevices[virtualDevices[i]]->Read(&pad[i], i);
}
}
#define NESTED(key, ...) StringHelper::Sprintf("Controllers.%s.Slot_%d." key, device->GetGuid().c_str(), slot, __VA_ARGS__)
void Ship::ControlDeck::LoadControllerSettings() {
std::shared_ptr<Mercury> Config = GlobalCtx2::GetInstance()->GetConfig();
for (auto const& val : Config->rjson["Controllers"]["Deck"].items()) {
int slot = std::stoi(val.key().substr(5));
for (size_t dev = 0; dev < physicalDevices.size(); dev++) {
std::string guid = physicalDevices[dev]->GetGuid();
if(guid != val.value()) continue;
virtualDevices[slot] = dev;
}
}
for (size_t i = 0; i < virtualDevices.size(); i++) {
std::shared_ptr<Controller> backend = physicalDevices[virtualDevices[i]];
Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid());
}
for (const auto& device : physicalDevices) {
std::string guid = device->GetGuid();
for (int slot = 0; slot < MAXCONTROLLERS; slot++) {
if (!(Config->rjson["Controllers"].contains(guid) && Config->rjson["Controllers"][guid].contains(StringHelper::Sprintf("Slot_%d", slot)))) continue;
auto& profile = device->profiles[slot];
auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", slot)];
profile.Mappings.clear();
profile.Thresholds.clear();
profile.GyroThresholds.clear();
profile.UseRumble = Config->getBool(NESTED("Rumble.Enabled", ""));
profile.RumbleStrength = Config->getBool(NESTED("Rumble.Strength", ""));
profile.UseGyro = Config->getBool(NESTED("Gyro.Enabled", ""));
for (auto const& val : rawProfile["Gyro"]["Thresholds"].items()) {
profile.GyroThresholds[std::stoi(val.key())] = val.value();
}
for (auto const& val : rawProfile["Thresholds"].items()) {
profile.Thresholds[static_cast<ControllerThresholds>(std::stoi(val.key()))] = val.value();
}
for (auto const& val : rawProfile["Mappings"].items()) {
device->SetButtonMapping(slot, std::stoi(val.key().substr(4)), val.value());
}
}
}
}
void Ship::ControlDeck::SaveControllerSettings() {
std::shared_ptr<Mercury> Config = GlobalCtx2::GetInstance()->GetConfig();
for (size_t i = 0; i < virtualDevices.size(); i++) {
std::shared_ptr<Controller> backend = physicalDevices[virtualDevices[i]];
Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid());
}
for (const auto& device : physicalDevices) {
int slot = 0;
std::string guid = device->GetGuid();
for (const auto& profile : device->profiles) {
if (!device->Connected()) continue;
auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", slot)];
Config->setBool(NESTED("Rumble.Enabled", ""), profile.UseRumble);
Config->setFloat(NESTED("Rumble.Strength", ""), profile.RumbleStrength);
Config->setBool(NESTED("Gyro.Enabled", ""), profile.UseGyro);
for (auto const& val : rawProfile["Mappings"].items()) {
Config->setInt(NESTED("Mappings.%s", val.key().c_str()), -1);
}
for (auto const& [key, val] : profile.GyroThresholds) {
Config->setInt(NESTED("Gyro.Thresholds.%d", key), val);
}
for (auto const& [key, val] : profile.Thresholds) {
Config->setInt(NESTED("Thresholds.%d", key), val);
}
for (auto const& [key, val] : profile.Mappings) {
Config->setInt(NESTED("Mappings.BTN_%d", val), key);
}
slot++;
}
}
Config->save();
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "Controller.h"
#include <vector>
#include <string>
namespace Ship {
class ControlDeck {
public:
std::vector<int> virtualDevices;
std::vector<std::shared_ptr<Controller>> physicalDevices = {};
void Init(uint8_t* controllerBits);
void ScanPhysicalDevices();
void WriteToPad(OSContPad* pad) const;
void LoadControllerSettings();
void SaveControllerSettings();
void SetPhysicalDevice(int slot, int deviceSlot);
};
}

View File

@ -1,28 +1,36 @@
#include "Controller.h"
#include "GlobalCtx2.h"
#include "stox.h"
#include <memory>
#include <algorithm>
#if __APPLE__
#include <SDL_events.h>
#else
#include <SDL2/SDL_events.h>
#endif
namespace Ship {
Controller::Controller(int32_t dwControllerNumber) : dwControllerNumber(dwControllerNumber) {
dwPressedButtons = 0;
wStickX = 0;
wStickY = 0;
wGyroX = 0;
wGyroY = 0;
Controller::Controller() : isRumbling(false), wStickX(0), wStickY(0), wGyroX(0), wGyroY(0), dwPressedButtons(0){
Attachment = nullptr;
profiles.resize(MAXCONTROLLERS);
for(int slot = 0; slot < MAXCONTROLLERS; slot++) {
dwPressedButtons.push_back(0);
}
}
void Controller::Read(OSContPad* pad) {
ReadFromSource();
void Controller::Read(OSContPad* pad, int32_t slot) {
ReadFromSource(slot);
pad->button |= dwPressedButtons & 0xFFFF;
SDL_PumpEvents();
// Button Inputs
pad->button |= dwPressedButtons[slot] & 0xFFFF;
// Stick Inputs
if (pad->stick_x == 0) {
if (dwPressedButtons & BTN_STICKLEFT) {
if (dwPressedButtons[slot] & BTN_STICKLEFT) {
pad->stick_x = -128;
}
else if (dwPressedButtons & BTN_STICKRIGHT) {
else if (dwPressedButtons[slot] & BTN_STICKRIGHT) {
pad->stick_x = 127;
}
else {
@ -31,10 +39,10 @@ namespace Ship {
}
if (pad->stick_y == 0) {
if (dwPressedButtons & BTN_STICKDOWN) {
if (dwPressedButtons[slot] & BTN_STICKDOWN) {
pad->stick_y = -128;
}
else if (dwPressedButtons & BTN_STICKUP) {
else if (dwPressedButtons[slot] & BTN_STICKUP) {
pad->stick_y = 127;
}
else {
@ -42,55 +50,38 @@ namespace Ship {
}
}
// Stick Inputs
if (pad->cam_x == 0) {
if (dwPressedButtons[slot] & BTN_VSTICKLEFT) {
pad->cam_x = -128 * 10.0f;
}
else if (dwPressedButtons[slot] & BTN_VSTICKRIGHT) {
pad->cam_x = 127 * 10.0f;
}
else {
pad->cam_x = wCamX;
}
}
if (pad->cam_y == 0) {
if (dwPressedButtons[slot] & BTN_VSTICKDOWN) {
pad->cam_y = -128 * 10.0f;
}
else if (dwPressedButtons[slot] & BTN_VSTICKUP) {
pad->cam_y = 127 * 10.0f;
}
else {
pad->cam_y = wCamY;
}
}
// Gyro
pad->gyro_x = wGyroX;
pad->gyro_y = wGyroY;
}
void Controller::SetButtonMapping(const std::string& szButtonName, int32_t dwScancode) {
// Update the config value.
std::string ConfSection = GetBindingConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
Conf[ConfSection][szButtonName] = dwScancode;
// Reload the button mapping from Config
LoadBinding();
void Controller::SetButtonMapping(int slot, int32_t n64Button, int32_t dwScancode) {
std::map<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
std::erase_if(Mappings, [n64Button](const std::pair<int32_t, int32_t>& bin) { return bin.second == n64Button; });
Mappings[dwScancode] = n64Button;
}
void Controller::LoadBinding() {
std::string ConfSection = GetBindingConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CRIGHT)])] = BTN_CRIGHT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CLEFT)])] = BTN_CLEFT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CDOWN)])] = BTN_CDOWN;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CUP)])] = BTN_CUP;
//ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CRIGHT + "_2")])] = BTN_CRIGHT;
//ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CLEFT + "_2")])] = BTN_CLEFT;
//ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CDOWN + "_2")])] = BTN_CDOWN;
//ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CUP + "_2")])] = BTN_CUP;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_R)])] = BTN_R;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_L)])] = BTN_L;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DRIGHT)])] = BTN_DRIGHT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DLEFT)])] = BTN_DLEFT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DDOWN)])] = BTN_DDOWN;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DUP)])] = BTN_DUP;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_START)])] = BTN_START;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_Z)])] = BTN_Z;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_B)])] = BTN_B;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_A)])] = BTN_A;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKRIGHT)])] = BTN_STICKRIGHT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKLEFT)])] = BTN_STICKLEFT;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKDOWN)])] = BTN_STICKDOWN;
ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKUP)])] = BTN_STICKUP;
}
std::string Controller::GetConfSection() {
return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1);
}
std::string Controller::GetBindingConfSection() {
return GetControllerType() + " CONTROLLER BINDING " + std::to_string(GetControllerNumber() + 1);
}
}
}

View File

@ -1,51 +1,82 @@
#pragma once
#include <memory>
#include <map>
#include <memory>
#include <string>
#include <optional>
#include "stdint.h"
#include "UltraController.h"
#include "ControllerAttachment.h"
#include <vector>
#include <unordered_map>
#define EXTENDED_SCANCODE_BIT (1 << 8)
#define AXIS_SCANCODE_BIT (1 << 9)
namespace Ship {
enum ControllerThresholds {
LEFT_STICK = 1,
RIGHT_STICK = 2,
LEFT_TRIGGER = 3,
RIGHT_TRIGGER = 4,
DRIFT_X = 5,
DRIFT_Y = 6,
SENSITIVITY = 7,
GYRO_SENSITIVITY = 8
};
struct DeviceProfile {
bool UseRumble = false;
bool UseGyro = false;
float RumbleStrength = 1.0f;
std::unordered_map<ControllerThresholds, int32_t> Thresholds;
std::unordered_map<int32_t, int32_t> GyroThresholds;
std::map<int32_t, int32_t> Mappings;
};
class Controller {
public:
Controller(int32_t dwControllerNumber);
void Read(OSContPad* pad);
virtual void ReadFromSource() = 0;
virtual void WriteToSource(ControllerCallback* controller) = 0;
virtual ~Controller() = default;
Controller();
void Read(OSContPad* pad, int32_t slot);
virtual void ReadFromSource(int32_t slot) = 0;
virtual void WriteToSource(int32_t slot, ControllerCallback* controller) = 0;
virtual bool Connected() const = 0;
virtual bool CanRumble() const = 0;
virtual bool CanGyro() const = 0;
virtual void CreateDefaultBinding(int32_t slot) = 0;
bool isRumbling;
std::vector<DeviceProfile> profiles;
void SetButtonMapping(const std::string& szButtonName, int32_t dwScancode);
virtual void ClearRawPress() = 0;
virtual int32_t ReadRawPress() = 0;
void SetButtonMapping(int slot, int32_t n64Button, int32_t dwScancode);
std::shared_ptr<ControllerAttachment> GetAttachment() { return Attachment; }
int32_t GetControllerNumber() { return dwControllerNumber; }
virtual bool HasPadConf() const = 0;
virtual std::optional<std::string> GetPadConfSection() = 0;
std::string GetGuid() { return GUID; }
virtual const char* GetButtonName(int slot, int n64Button) = 0;
virtual const char* GetControllerName() = 0;
protected:
int32_t dwPressedButtons;
std::map<int32_t, int32_t> ButtonMapping;
int8_t wStickX;
int8_t wStickY;
float wGyroX;
float wGyroY;
virtual std::string GetControllerType() = 0;
virtual std::string GetConfSection() = 0;
virtual std::string GetBindingConfSection() = 0;
float wCamX;
float wCamY;
protected:
std::vector<int32_t> dwPressedButtons;
std::string GUID;
void LoadBinding();
private:
std::shared_ptr<ControllerAttachment> Attachment;
int32_t dwControllerNumber;
};
struct ControllerEntry {
uint8_t* controllerBits;
Controller* entryIO;
};
}

View File

@ -0,0 +1,277 @@
#include "InputEditor.h"
#include "Controller.h"
#include "Window.h"
#include "Lib/ImGui/imgui.h"
#include "ImGuiImpl.h"
#include "Utils/StringHelper.h"
#include "Lib/ImGui/imgui_internal.h"
namespace Ship {
extern "C" uint8_t __enableGameInput;
#define SEPARATION() ImGui::Dummy(ImVec2(0, 5))
void InputEditor::Init() {
BtnReading = -1;
}
std::shared_ptr<Controller> GetControllerPerSlot(int slot) {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
return Window::ControllerApi->physicalDevices[vDevices[slot]];
}
void InputEditor::DrawButton(const char* label, int n64Btn) {
const std::shared_ptr<Controller> backend = GetControllerPerSlot(CurrentPort);
float size = 40;
bool readingMode = BtnReading == n64Btn;
bool disabled = BtnReading != -1 && !readingMode || !backend->Connected();
ImVec2 len = ImGui::CalcTextSize(label);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - size));
ImGui::Text("%s", label);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
if(disabled) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
}
if(readingMode) {
const int32_t btn = backend->ReadRawPress();
if(btn != -1) {
backend->SetButtonMapping(CurrentPort, n64Btn, btn);
BtnReading = -1;
}
}
const char* BtnName = backend->GetButtonName(CurrentPort, n64Btn);
if (ImGui::Button(StringHelper::Sprintf("%s##HBTNID_%d", readingMode ? "Press a Key..." : BtnName, n64Btn).c_str())) {
BtnReading = n64Btn;
backend->ClearRawPress();
}
if(disabled) {
ImGui::PopItemFlag();
ImGui::PopStyleVar();
}
}
void InputEditor::DrawVirtualStick(const char* label, ImVec2 stick) {
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y));
ImGui::BeginChild(label, ImVec2(68, 75), false);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
float sz = 45.0f;
float rad = sz * 0.5f;
ImVec2 pos = ImVec2(p.x + sz * 0.5f + 12, p.y + sz * 0.5f + 11);
float stickX = (stick.x / 83.0f) * (rad * 0.5f);
float stickY = -(stick.y / 83.0f) * (rad * 0.5f);
ImVec4 rect = ImVec4(p.x + 2, p.y + 2, 65, 65);
draw_list->AddRect(ImVec2(rect.x, rect.y), ImVec2(rect.x + rect.z, rect.y + rect.w), ImColor(100, 100, 100, 255), 0.0f, 0, 1.5f);
draw_list->AddCircleFilled(pos, rad, ImColor(130, 130, 130, 255), 8);
draw_list->AddCircleFilled(ImVec2(pos.x + stickX, pos.y + stickY), 5, ImColor(15, 15, 15, 255), 7);
ImGui::EndChild();
}
void InputEditor::DrawControllerSchema() {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
const std::vector<std::shared_ptr<Controller>> devices = Window::ControllerApi->physicalDevices;
std::shared_ptr<Controller> Backend = devices[vDevices[CurrentPort]];
DeviceProfile& profile =Backend->profiles[CurrentPort];
float sensitivity = profile.Thresholds[SENSITIVITY];
bool IsKeyboard = Backend->GetGuid() == "Keyboard" || !Backend->Connected();
const char* ControllerName = Backend->GetControllerName();
if (ControllerName != nullptr && ImGui::BeginCombo("##ControllerEntries", ControllerName)) {
for (uint8_t i = 0; i < devices.size(); i++) {
if (ImGui::Selectable(devices[i]->GetControllerName(), i == vDevices[CurrentPort])) {
Window::ControllerApi->SetPhysicalDevice(CurrentPort, i);
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if(ImGui::Button("Refresh")) {
Window::ControllerApi->ScanPhysicalDevices();
}
SohImGui::BeginGroupPanel("Buttons", ImVec2(150, 20));
DrawButton("A", BTN_A);
DrawButton("B", BTN_B);
DrawButton("L", BTN_L);
DrawButton("R", BTN_R);
DrawButton("Z", BTN_Z);
DrawButton("START", BTN_START);
SEPARATION();
SohImGui::EndGroupPanel(IsKeyboard ? 7.0f : 48.0f);
ImGui::SameLine();
SohImGui::BeginGroupPanel("Digital Pad", ImVec2(150, 20));
DrawButton("Up", BTN_DUP);
DrawButton("Down", BTN_DDOWN);
DrawButton("Left", BTN_DLEFT);
DrawButton("Right", BTN_DRIGHT);
SEPARATION();
SohImGui::EndGroupPanel(IsKeyboard ? 53.0f : 94.0f);
ImGui::SameLine();
SohImGui::BeginGroupPanel("Analog Stick", ImVec2(150, 20));
DrawButton("Up", BTN_STICKUP);
DrawButton("Down", BTN_STICKDOWN);
DrawButton("Left", BTN_STICKLEFT);
DrawButton("Right", BTN_STICKRIGHT);
if (!IsKeyboard) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
DrawVirtualStick("##MainVirtualStick", ImVec2(Backend->wStickX, Backend->wStickY));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##MSInput", ImVec2(90, 50), false);
ImGui::Text("Deadzone");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MDZone", &profile.Thresholds[LEFT_STICK]);
ImGui::PopItemWidth();
ImGui::EndChild();
} else {
ImGui::Dummy(ImVec2(0, 6));
}
SohImGui::EndGroupPanel(IsKeyboard ? 52.0f : 24.0f);
ImGui::SameLine();
if (!IsKeyboard) {
ImGui::SameLine();
SohImGui::BeginGroupPanel("Camera Stick", ImVec2(150, 20));
DrawButton("Up", BTN_VSTICKUP);
DrawButton("Down", BTN_VSTICKDOWN);
DrawButton("Left", BTN_VSTICKLEFT);
DrawButton("Right", BTN_VSTICKRIGHT);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
DrawVirtualStick("##CameraVirtualStick", ImVec2(Backend->wCamX / sensitivity, Backend->wCamY / sensitivity));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##CSInput", ImVec2(90, 85), false);
ImGui::Text("Deadzone");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MDZone", &profile.Thresholds[RIGHT_STICK]);
ImGui::PopItemWidth();
ImGui::Text("Sensitivity");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MSensitivity", &profile.Thresholds[SENSITIVITY]);
ImGui::PopItemWidth();
ImGui::EndChild();
SohImGui::EndGroupPanel(14.0f);
}
if(Backend->CanGyro()) {
ImGui::SameLine();
SohImGui::BeginGroupPanel("Gyro Options", ImVec2(175, 20));
float cursorX = ImGui::GetCursorPosX() + 5;
ImGui::SetCursorPosX(cursorX);
ImGui::Checkbox("Enable Gyro", &profile.UseGyro);
ImGui::SetCursorPosX(cursorX);
ImGui::Text("Gyro Sensitivity: %d%%", profile.Thresholds[GYRO_SENSITIVITY]);
ImGui::PushItemWidth(135.0f);
ImGui::SetCursorPosX(cursorX);
ImGui::SliderInt("##GSensitivity", &profile.Thresholds[GYRO_SENSITIVITY], 0, 100, "");
ImGui::PopItemWidth();
ImGui::Dummy(ImVec2(0, 1));
ImGui::SetCursorPosX(cursorX);
if (ImGui::Button("Recalibrate Gyro##RGyro")) {
profile.Thresholds[DRIFT_X] = 0;
profile.Thresholds[DRIFT_Y] = 0;
}
ImGui::SetCursorPosX(cursorX);
DrawVirtualStick("##GyroPreview", ImVec2(Backend->wGyroX, Backend->wGyroY));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##GyInput", ImVec2(90, 85), false);
ImGui::Text("Drift X");
ImGui::PushItemWidth(80);
ImGui::InputInt("##GDriftX", &profile.Thresholds[DRIFT_X]);
ImGui::PopItemWidth();
ImGui::Text("Drift Y");
ImGui::PushItemWidth(80);
ImGui::InputInt("##GDriftY", &profile.Thresholds[DRIFT_Y]);
ImGui::PopItemWidth();
ImGui::EndChild();
SohImGui::EndGroupPanel(14.0f);
}
ImGui::SameLine();
const ImVec2 cursor = ImGui::GetCursorPos();
SohImGui::BeginGroupPanel("C-Buttons", ImVec2(158, 20));
DrawButton("Up", BTN_CUP);
DrawButton("Down", BTN_CDOWN);
DrawButton("Left", BTN_CLEFT);
DrawButton("Right", BTN_CRIGHT);
ImGui::Dummy(ImVec2(0, 5));
SohImGui::EndGroupPanel();
ImGui::SetCursorPosX(cursor.x);
ImGui::SetCursorPosY(cursor.y + 120);
SohImGui::BeginGroupPanel("Options", ImVec2(158, 20));
float cursorX = ImGui::GetCursorPosX() + 5;
ImGui::SetCursorPosX(cursorX);
ImGui::Checkbox("Rumble Enabled", &profile.UseRumble);
if (Backend->CanRumble()) {
ImGui::SetCursorPosX(cursorX);
ImGui::Text("Rumble Force: %d%%", static_cast<int>(100 * profile.RumbleStrength));
ImGui::SetCursorPosX(cursorX);
ImGui::PushItemWidth(135.0f);
ImGui::SliderFloat("##RStrength", &profile.RumbleStrength, 0, 1.0f, "");
ImGui::PopItemWidth();
}
ImGui::Dummy(ImVec2(0, 5));
SohImGui::EndGroupPanel(IsKeyboard ? 0.0f : 2.0f);
}
void InputEditor::DrawHud() {
__enableGameInput = true;
if (!this->Opened) {
BtnReading = -1;
return;
}
ImGui::SetNextWindowSizeConstraints(ImVec2(641, 250), ImVec2(1200, 290));
//OTRTODO: Disable this stupid workaround ( ReadRawPress() only works when the window is on the main viewport )
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
ImGui::Begin("Controller Configuration", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize);
ImGui::BeginTabBar("##Controllers");
for (int i = 0; i < 4; i++) {
if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d", i + 1).c_str())) {
CurrentPort = i;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
// Draw current cfg
DrawControllerSchema();
ImGui::End();
}
}

View File

@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include <PR/ultra64/gbi.h>
#include "imgui_internal.h"
std::map<std::string, std::unique_ptr<CVar>, std::less<>> cvars;
@ -64,13 +65,13 @@ void CVar_SetFloat(const char* name, float value) {
cvar->value.valueFloat = value;
}
void CVar_SetString(const char* name, const char* value) {
extern "C" void CVar_SetString(const char* name, const char* value) {
auto& cvar = cvars[name];
if (!cvar) {
cvar = std::make_unique<CVar>();
}
cvar->type = CVAR_TYPE_STRING;
cvar->value.valueStr = value;
cvar->value.valueStr = ImStrdup(value);
}
extern "C" void CVar_RegisterS32(const char* name, s32 defaultValue) {

View File

@ -27,6 +27,7 @@ s32 CVar_GetS32(const char* name, s32 defaultValue);
float CVar_GetFloat(const char* name, float defaultValue);
const char* CVar_GetString(const char* name, const char* defaultValue);
void CVar_SetS32(const char* name, s32 value);
void CVar_SetString(const char* name, const char* value);
void CVar_RegisterS32(const char* name, s32 defaultValue);
void CVar_RegisterFloat(const char* name, float defaultValue);
@ -44,6 +45,5 @@ void CVar_RegisterString(const char* name, const char* defaultValue);
extern std::map<std::string, std::unique_ptr<CVar>, std::less<>> cvars;
void CVar_SetFloat(const char* name, float value);
void CVar_SetString(const char* name, const char* value);
#endif
#endif

View File

@ -0,0 +1,31 @@
#pragma once
#include <vector>
#include <optional>
#include "Controller.h"
class DisconnectedController final : public Ship::Controller {
public:
DisconnectedController() {
GUID = "Disconnected";
}
std::map<std::vector<std::string>, int32_t> ReadButtonPress();
void ReadFromSource(int32_t slot) override {}
const char* GetControllerName() override { return "Disconnected"; }
const char* GetButtonName(int slot, int n64Button) override { return "None"; }
void WriteToSource(int32_t slot, ControllerCallback* controller) override { }
bool Connected() const override { return false; }
bool CanRumble() const override { return false; }
bool CanGyro() const override { return false; }
void ClearRawPress() override {}
int32_t ReadRawPress() override { return -1; }
bool HasPadConf() const { return true; }
std::optional<std::string> GetPadConfSection() { return "Unk"; }
void CreateDefaultBinding(int32_t slot) override {}
protected:
std::string GetControllerType() { return "Unk"; }
std::string GetConfSection() { return "Unk"; }
std::string GetBindingConfSection() { return "Unk"; }
};

View File

@ -7,7 +7,6 @@
#include <PR/ultra64/pi.h>
#include <PR/ultra64/message.h>
#include "ConfigFile.h"
#include "Cvar.h"
#include "GlobalCtx2.h"
#include "ImGuiImpl.h"
@ -33,18 +32,6 @@ namespace Game {
Audio_SetGameVolume(SEQ_SFX, CVar_GetFloat("gFanfareVolume", 1));
}
void LoadPadSettings() {
const std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf;
for (const auto& [i, controllers] : Ship::Window::Controllers) {
for (const auto& controller : controllers) {
if (auto padConfSection = controller->GetPadConfSection()) {
}
}
}
}
void LoadSettings() {
DebugConsole_LoadCVars();
}
@ -56,8 +43,9 @@ namespace Game {
void InitSettings() {
ModInternal::RegisterHook<ModInternal::AudioInit>(UpdateAudio);
ModInternal::RegisterHook<ModInternal::GfxInit>([] {
gfx_get_current_rendering_api()->set_texture_filter((FilteringMode) CVar_GetS32("gTextureFilter", THREE_POINT));
gfx_get_current_rendering_api()->set_texture_filter((FilteringMode) CVar_GetS32("gTextureFilter", FILTER_THREE_POINT));
SohImGui::console->opened = CVar_GetS32("gConsoleEnabled", 0);
SohImGui::controller->Opened = CVar_GetS32("gControllerConfigurationEnabled", 0);
UpdateAudio();
});
}

View File

@ -8,6 +8,9 @@
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/sohconsole_sink.h"
#include "ModManager.h"
#ifdef __APPLE__
#include "OSXFolderManager.h"
#endif
namespace Ship {
std::weak_ptr<GlobalCtx2> GlobalCtx2::Context;
@ -29,7 +32,23 @@ namespace Ship {
return GetInstance();
}
GlobalCtx2::GlobalCtx2(const std::string& Name) : Name(Name), MainPath(""), PatchesPath("") {
std::string GlobalCtx2::GetAppDirectoryPath() {
#ifdef __APPLE__
FolderManager folderManager;
std::string fpath = std::string(folderManager.pathForDirectory(NSApplicationSupportDirectory, NSUserDomainMask));
fpath.append("/com.shipofharkinian.soh");
return fpath;
#endif
return ".";
}
std::string GlobalCtx2::GetPathRelativeToAppDirectory(const char* path) {
return GlobalCtx2::GetAppDirectoryPath() + "/" + path;
}
GlobalCtx2::GlobalCtx2(std::string Name) : Name(std::move(Name)) {
}
@ -40,22 +59,19 @@ namespace Ship {
void GlobalCtx2::InitWindow() {
InitLogging();
Config = std::make_shared<ConfigFile>(GlobalCtx2::GetInstance(), "shipofharkinian.ini");
MainPath = (*Config)["ARCHIVE"]["Main Archive"];
if (MainPath.empty()) {
MainPath = "oot.otr";
}
PatchesPath = (*Config)["ARCHIVE"]["Patches Directory"];
if (PatchesPath.empty()) {
PatchesPath = "./";
}
ResMan = std::make_shared<ResourceMgr>(GlobalCtx2::GetInstance(), MainPath, PatchesPath);
Win = std::make_shared<Window>(GlobalCtx2::GetInstance());
Config = std::make_shared<Mercury>(GetPathRelativeToAppDirectory("shipofharkinian.json"));
Config->reload();
MainPath = Config->getString("Game.Main Archive", GetPathRelativeToAppDirectory("oot.otr"));
PatchesPath = Config->getString("Game.Patches Archive", GetAppDirectoryPath() + "/mods");
ResMan = std::make_shared<ResourceMgr>(GetInstance(), MainPath, PatchesPath);
Win = std::make_shared<Window>(GetInstance());
if (!ResMan->DidLoadSuccessfully())
{
#ifdef _WIN32
MessageBox(NULL, L"Main OTR file not found!", L"Uh oh", MB_OK);
MessageBox(nullptr, L"Main OTR file not found!", L"Uh oh", MB_OK);
#else
SPDLOG_ERROR("Main OTR file not found!");
#endif
@ -67,11 +83,13 @@ namespace Ship {
void GlobalCtx2::InitLogging() {
try {
auto logPath = GetPathRelativeToAppDirectory(("logs/" + GetName() + ".log").c_str());
// Setup Logging
spdlog::init_thread_pool(8192, 1);
auto SohConsoleSink = std::make_shared<spdlog::sinks::soh_sink_mt>();
auto ConsoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto FileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("logs/" + GetName() + ".log", 1024 * 1024 * 10, 10);
auto FileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(logPath, 1024 * 1024 * 10, 10);
SohConsoleSink->set_level(spdlog::level::trace);
ConsoleSink->set_level(spdlog::level::trace);
FileSink->set_level(spdlog::level::trace);
@ -87,7 +105,7 @@ namespace Ship {
}
}
void GlobalCtx2::WriteSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size) {
void GlobalCtx2::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, const size_t size) {
std::ofstream saveFile = std::ofstream(savePath, std::fstream::in | std::fstream::out | std::fstream::binary);
saveFile.seekp(addr);
saveFile.write((char*)dramAddr, size);
@ -107,4 +125,4 @@ namespace Ship {
saveFile.close();
}
}
}

View File

@ -6,8 +6,9 @@
#ifdef __cplusplus
#include <filesystem>
#include <memory>
#include <fstream>
#include "spdlog/spdlog.h"
#include "ConfigFile.h"
#include "Lib/Mercury/Mercury.h"
namespace Ship {
class ResourceMgr;
@ -22,12 +23,15 @@ namespace Ship {
std::shared_ptr<Window> GetWindow() { return Win; }
std::shared_ptr<ResourceMgr> GetResourceManager() { return ResMan; }
std::shared_ptr<spdlog::logger> GetLogger() { return Logger; }
std::shared_ptr<ConfigFile> GetConfig() { return Config; }
std::shared_ptr<Mercury> GetConfig() { return Config; }
void WriteSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size);
static std::string GetAppDirectoryPath();
static std::string GetPathRelativeToAppDirectory(const char* path);
void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size);
void ReadSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size);
GlobalCtx2(const std::string& Name);
GlobalCtx2(std::string Name);
~GlobalCtx2();
protected:
@ -38,7 +42,7 @@ namespace Ship {
static std::weak_ptr <GlobalCtx2> Context;
std::shared_ptr<spdlog::logger> Logger;
std::shared_ptr<Window> Win;
std::shared_ptr<ConfigFile> Config; // Config needs to be after the Window because we call the Window during it's destructor.
std::shared_ptr<Mercury> Config; // Config needs to be after the Window because we call the Window during it's destructor.
std::shared_ptr<ResourceMgr> ResMan;
std::string Name;
std::string MainPath;

View File

@ -5,6 +5,7 @@
#include <functional>
#include "UltraController.h"
#include "Controller.h"
#define DEFINE_HOOK(name, type) struct name { typedef std::function<type> fn; }
@ -28,12 +29,11 @@ namespace ModInternal {
}
DEFINE_HOOK(ControllerRead, void(OSContPad* cont_pad));
DEFINE_HOOK(ControllerRawInput, void(Ship::Controller* backend, uint32_t raw));
DEFINE_HOOK(AudioInit, void());
DEFINE_HOOK(LoadTexture, void(const char* path, uint8_t** texture));
DEFINE_HOOK(GfxInit, void());
DEFINE_HOOK(ExitGame, void());
}

View File

@ -12,6 +12,7 @@
#include "GameSettings.h"
#include "Console.h"
#include "Hooks.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "Lib/ImGui/imgui_internal.h"
#include "GlobalCtx2.h"
#include "ResourceMgr.h"
@ -63,34 +64,30 @@ namespace SohImGui {
ImGuiIO* io;
Console* console = new Console;
GameOverlay* overlay = new GameOverlay;
InputEditor* controller = new InputEditor;
static ImVector<ImRect> s_GroupPanelLabelStack;
bool p_open = false;
bool needs_save = false;
const char* RainbowColorCvarList[] = {
//This is the list of possible CVars that has rainbow effect.
"gTunic_Kokiri_", "gTunic_Goron_", "gTunic_Zora_",
"gFireArrowCol", "gIceArrowCol", "gTunic_Zora_",
"gFireArrowColEnv", "gIceArrowColEnv", "gLightArrowColEnv",
"gCCHeartsPrim", "gDDCCHeartsPrim", "gLightArrowCol",
"gCCABtnPrim", "gCCBBtnPrim", "gCCCBtnPrim", "gCCStartBtnPrim",
"gCCCUBtnPrim", "gCCCLBtnPrim", "gCCCRBtnPrim", "gCCCDBtnPrim", "gCCDpadPrim",
"gCCMagicBorderNormPrim", "gCCMagicBorderPrim", "gCCMagicPrim", "gCCMagicUsePrim",
"gCCMinimapPrim", "gCCMinimapDGNPrim", "gCCMinimapCPPrim", "gCCMinimapLEPrim",
"gCCRupeePrim", "gCCKeysPrim", "gDog1Col", "gDog2Col", "gCCVSOAPrim",
"gKeese1_Ef_Prim","gKeese2_Ef_Prim","gKeese1_Ef_Env","gKeese2_Ef_Env",
"gDF_Col", "gDF_Env",
"gNL_Diamond_Col", "gNL_Diamond_Env", "gNL_Orb_Col", "gNL_Orb_Env",
"gTrailCol", "gCharged1Col", "gCharged1ColEnv", "gCharged2Col", "gCharged2ColEnv",
"gCCFileChoosePrim", "gCCFileChooseTextPrim", "gCCEquipmentsPrim", "gCCItemsPrim",
"gCCMapsPrim", "gCCQuestsPrim", "gCCSavePrim", "gCCGameoverPrim",
};
const char* filters[3] = {
"Three-Point",
"Linear",
"None"
};
const char* powers[9] = {
"Vanilla (1x)",
"Double (2x)",
"Quadruple (4x)",
"Octuple (8x)",
"Hexadecuple (16x)",
"Duotrigintuple (32x)",
"Quattuorsexagintuple (64x)",
"Octoviginticentuple (128x)",
"Hexaquinquagintiducentuple (256x)"
};
std::map<std::string, std::vector<std::string>> hiddenwindowCategories;
std::map<std::string, std::vector<std::string>> windowCategories;
std::map<std::string, CustomWindow> customWindows;
@ -248,50 +245,6 @@ namespace SohImGui {
stbi_image_free(img_data);
}
void LoadRainbowColor() {
u8 arrayLength = sizeof(RainbowColorCvarList) / sizeof(*RainbowColorCvarList);
for (u8 s = 0; s < arrayLength; s++) {
std::string cvarName = RainbowColorCvarList[s];
std::string Cvar_Red = cvarName;
Cvar_Red += "R";
std::string Cvar_Green = cvarName;
Cvar_Green += "G";
std::string Cvar_Blue = cvarName;
Cvar_Blue += "B";
std::string Cvar_RBM = cvarName;
Cvar_RBM += "RBM";
std::string RBM_HUE = cvarName;
RBM_HUE += "Hue";
f32 Canon = 10.f * s;
ImVec4 NewColor;
const f32 deltaTime = 1.0f / ImGui::GetIO().Framerate;
f32 hue = CVar_GetFloat(RBM_HUE.c_str(), 0.0f);
f32 newHue = hue + CVar_GetS32("gColorRainbowSpeed", 1) * 36.0f * deltaTime;
if (newHue >= 360)
newHue = 0;
CVar_SetFloat(RBM_HUE.c_str(), newHue);
f32 current_hue = CVar_GetFloat(RBM_HUE.c_str(), 0);
u8 i = current_hue / 60 + 1;
u8 a = (-current_hue / 60.0f + i) * 255;
u8 b = (current_hue / 60.0f + (1 - i)) * 255;
switch (i) {
case 1: NewColor.x = 255; NewColor.y = b; NewColor.z = 0; break;
case 2: NewColor.x = a; NewColor.y = 255; NewColor.z = 0; break;
case 3: NewColor.x = 0; NewColor.y = 255; NewColor.z = b; break;
case 4: NewColor.x = 0; NewColor.y = a; NewColor.z = 255; break;
case 5: NewColor.x = b; NewColor.y = 0; NewColor.z = 255; break;
case 6: NewColor.x = 255; NewColor.y = 0; NewColor.z = a; break;
}
if (CVar_GetS32(Cvar_RBM.c_str(), 0) != 0) {
CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(NewColor.x, 0, 255));
CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(NewColor.y, 0, 255));
CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(NewColor.z, 0, 255));
}
}
}
void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) {
std::string Cvar_Red = cvarname;
Cvar_Red += "R";
@ -366,11 +319,17 @@ namespace SohImGui {
SohImGui::overlay->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu");
}
auto imguiIniPath = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("imgui.ini");
auto imguiLogPath = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("imgui_log.txt");
io->IniFilename = strcpy(new char[imguiIniPath.length() + 1], imguiIniPath.c_str());
io->LogFilename = strcpy(new char[imguiLogPath.length() + 1], imguiLogPath.c_str());
if (UseViewports()) {
io->ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
}
console->Init();
overlay->Init();
controller->Init();
ImGuiWMInit();
ImGuiBackendInit();
@ -389,19 +348,19 @@ namespace SohImGui {
LoadTexture("C-Right", "assets/ship_of_harkinian/buttons/CRight.png");
LoadTexture("C-Up", "assets/ship_of_harkinian/buttons/CUp.png");
LoadTexture("C-Down", "assets/ship_of_harkinian/buttons/CDown.png");
});
for (const auto& [i, controllers] : Ship::Window::Controllers)
{
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", i).c_str(), 0);
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", i).c_str(), 0);
needs_save = true;
}
});
ModInternal::RegisterHook<ModInternal::ControllerRead>([](OSContPad* cont_pad) {
pads = cont_pad;
});
});
Game::InitSettings();
CVar_SetS32("gRandoGenerating", 0);
CVar_SetS32("gNewSeedGenerated", 0);
CVar_SetS32("gNewFileDropped", 0);
CVar_SetString("gDroppedFile", "None");
// Game::SaveSettings();
}
void Update(EventImpl event) {
@ -492,9 +451,9 @@ namespace SohImGui {
}
}
void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format)
void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue)
{
int val = CVar_GetS32(cvarName, 0);
int val = CVar_GetS32(cvarName, defaultValue);
ImGui::Text(text, val);
@ -550,7 +509,7 @@ namespace SohImGui {
}
void EnhancementCombo(const std::string& name, const char* cvarName, const std::vector<std::string>& items, int defaultValue) {
if (ImGui::BeginCombo(name.c_str(), items[static_cast<int>(CVar_GetS32(cvarName, defaultValue))].c_str())) {
for (int settingIndex = 0; settingIndex < (int) items.size(); settingIndex++) {
if (ImGui::Selectable(items[settingIndex].c_str())) {
@ -632,7 +591,7 @@ namespace SohImGui {
CVar_SetS32(Cvar_RBM.c_str(), 0); //On click disable rainbow mode.
needs_save = true;
}
Tooltip("Revert colors to the game original colors (GameCube version)\nOverwrites previously chosen color");
Tooltip("Revert colors to the game's original colors (GameCube version)\nOverwrites previously chosen color");
}
void EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow, bool has_alpha, bool TitleSameLine) {
@ -662,8 +621,7 @@ namespace SohImGui {
CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(ColorRGBA.z * 255, 0, 255));
needs_save = true;
}
}
else {
} else {
if (ImGui::ColorEdit4(text, (float*)&ColorRGBA, flags)) {
CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(ColorRGBA.x * 255, 0, 255));
CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(ColorRGBA.y * 255, 0, 255));
@ -718,12 +676,12 @@ namespace SohImGui {
ImGui::DockBuilderRemoveNode(dockId);
ImGui::DockBuilderAddNode(dockId, ImGuiDockNodeFlags_NoTabBar);
ImGui::DockBuilderDockWindow("OoT Master Quest", dockId);
ImGui::DockBuilderDockWindow("Main Game", dockId);
ImGui::DockBuilderFinish(dockId);
}
ImGui::DockSpace(dockId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None);
ImGui::DockSpace(dockId, ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_NoDockingInCentralNode);
if (ImGui::IsKeyPressed(TOGGLE_BTN)) {
bool menu_bar = CVar_GetS32("gOpenMenuBar", 0);
@ -731,36 +689,32 @@ namespace SohImGui {
needs_save = true;
GlobalCtx2::GetInstance()->GetWindow()->dwMenubar = menu_bar;
ShowCursor(menu_bar, Dialogues::dMenubar);
GlobalCtx2::GetInstance()->GetWindow()->GetControlDeck()->SaveControllerSettings();
if (CVar_GetS32("gControlNav", 0)) {
if (CVar_GetS32("gOpenMenuBar", 0)) {
io->ConfigFlags |=ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_NavEnableKeyboard;
} else {
io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
}
else
{
io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
}
}
else
{
io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
} else {
io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
}
}
#if __APPLE__
if ((ImGui::IsKeyDown(ImGuiKey_LeftSuper) ||
ImGui::IsKeyDown(ImGuiKey_RightSuper)) &&
ImGui::IsKeyDown(ImGuiKey_RightSuper)) &&
ImGui::IsKeyPressed(ImGuiKey_R, false)) {
console->Commands["reset"].handler(emptyArgs);
}
#else
if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_R, false)) {
console->Commands["reset"].handler(emptyArgs);
}
#endif
if (ImGui::BeginMenuBar()) {
if (DefaultAssets.contains("Game_Icon")) {
ImGui::SetCursorPos(ImVec2(5, 2.5f));
@ -780,7 +734,11 @@ namespace SohImGui {
console->Commands["reset"].handler(emptyArgs);
}
ImGui::EndMenu();
}
}
ImGui::Separator();
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Audio")) {
EnhancementSliderFloat("Master Volume: %d %%", "##Master_Vol", "gGameMasterVolume", 0.0f, 1.0f, "", 1.0f, true);
@ -793,70 +751,45 @@ namespace SohImGui {
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Controller"))
{
EnhancementCheckbox("Use Controller Navigation", "gControlNav");
Tooltip("Allows controller navigation of the menu bar\nD-pad to move between items, A to select, and X to grab focus on the menu bar");
EnhancementCheckbox("Controller Configuration", "gControllerConfigurationEnabled");
controller->Opened = CVar_GetS32("gControllerConfigurationEnabled", 0);
ImGui::Separator();
// TODO mutual exclusions -- gDpadEquips and gDpadPauseName cause conflicts, but nothing stops a user from selecting both
// There should be some system to prevent conclifting enhancements from being selected
// TODO mutual exclusions -- There should be some system to prevent conclifting enhancements from being selected
EnhancementCheckbox("D-pad Support on Pause and File Select", "gDpadPauseName");
Tooltip("Enables Pause and File Select screen navigation with the D-pad\nIf used with D-pad as Equip Items, you must hold C-Up\nto equip instead of navigate");
EnhancementCheckbox("D-pad Support in Ocarina and Text Choice", "gDpadOcarinaText");
EnhancementCheckbox("D-pad Support for Browsing Shop Items", "gDpadShop");
EnhancementCheckbox("D-pad as Equip Items", "gDpadEquips");
Tooltip("Allows the D-pad to be used as extra C buttons");
ImGui::Separator();
EnhancementCheckbox("Show Inputs", "gInputEnabled");
Tooltip("Shows currently pressed inputs on the bottom right of the screen");
EnhancementCheckbox("Rumble Enabled", "gRumbleEnabled");
EnhancementSliderFloat("Input Scale: %.1f", "##Input", "gInputScale", 1.0f, 3.0f, "", 1.0f, false);
Tooltip("Sets the on screen size of the displayed inputs from Show Inputs");
ImGui::Separator();
for (const auto& [i, controllers] : Ship::Window::Controllers)
{
bool hasPad = std::find_if(controllers.begin(), controllers.end(), [](const auto& c) {
return c->HasPadConf() && c->Connected();
}) != controllers.end();
if (!hasPad) continue;
auto menuLabel = "Controller " + std::to_string(i + 1);
if (ImGui::BeginMenu(menuLabel.c_str()))
{
EnhancementSliderFloat("Gyro Sensitivity: %d %%", "##GYROSCOPE", StringHelper::Sprintf("gCont%i_GyroSensitivity", i).c_str(), 0.0f, 1.0f, "", 1.0f, true);
if (ImGui::Button("Recalibrate Gyro"))
{
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", i).c_str(), 0);
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", i).c_str(), 0);
needs_save = true;
}
ImGui::Separator();
EnhancementSliderFloat("Rumble Strength: %d %%", "##RUMBLE", StringHelper::Sprintf("gCont%i_RumbleStrength", i).c_str(), 0.0f, 1.0f, "", 1.0f, true);
ImGui::EndMenu();
}
ImGui::Separator();
}
Tooltip("Sets the on screen size of the displayed inputs from the Show Inputs setting");
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Graphics"))
{
EnhancementSliderInt("Internal Resolution: %dx", "##IMul", "gInternalResolution", 1, 8, "");
Tooltip("Increases the render resolution of the game, up to 8x your output resolution,\nas a more intensive but effective form of anti-aliasing");
gfx_current_dimensions.internal_mul = CVar_GetS32("gInternalResolution", 1);
EnhancementSliderFloat("Internal Resolution: %d %%", "##IMul", "gInternalResolution", 0.5f, 2.0f, "", 1.0f, true);
Tooltip("Multiplies your output resolution by the value inputted,\nas a more intensive but effective form of anti-aliasing");
gfx_current_dimensions.internal_mul = CVar_GetFloat("gInternalResolution", 1);
EnhancementSliderInt("MSAA: %d", "##IMSAA", "gMSAAValue", 1, 8, "");
Tooltip("Activates anti-aliasing when above 1, up to 8x for 8 samples for every pixel");
Tooltip("Activates multi-sample anti-aliasing when above 1x\nup to 8x for 8 samples for every pixel");
gfx_msaa_level = CVar_GetS32("gMSAAValue", 1);
if (impl.backend == Backend::DX11)
@ -882,9 +815,11 @@ namespace SohImGui {
}
Tooltip("When Interpolation FPS setting is at least this threshold,\n"
"add one frame of input lag (e.g. 16.6 ms for 60 FPS) in order to avoid jitter.\n"
"This setting allows the CPU to work on one frame while GPU works on the previous frame.\n"
"This setting should be used when your computer is too slow to do CPU + GPU work in time.");
"add one frame of input lag (e.g. 16.6 ms for 60 FPS)\n"
"in order to avoid jitter.This setting allows the CPU\n"
"to work on one frame while GPU works on the previous frame.\n"
"This setting should be used when your computer is too slow\n"
"to do CPU + GPU work in time.");
}
EXPERIMENTAL();
@ -896,6 +831,8 @@ namespace SohImGui {
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Languages")) {
EnhancementRadioButton("English", "gLanguages", 0);
EnhancementRadioButton("German", "gLanguages", 1);
@ -903,6 +840,8 @@ namespace SohImGui {
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Enhancements"))
{
if (ImGui::BeginMenu("Gameplay"))
@ -912,40 +851,66 @@ namespace SohImGui {
EnhancementSliderInt("Text Speed: %dx", "##TEXTSPEED", "gTextSpeed", 1, 5, "");
EnhancementSliderInt("King Zora Speed: %dx", "##MWEEPSPEED", "gMweepSpeed", 1, 5, "");
EnhancementSliderInt("Biggoron Forge Time: %d days", "##FORGETIME", "gForgeTime", 0, 3, "");
Tooltip("Allows you to change the number of days it takes for Biggoron to forge the Biggoron Sword");
Tooltip("Allows you to change the number of days it takes for\nBiggoron to forge the Biggoron Sword");
EnhancementSliderInt("Vine/Ladder Climb speed +%d", "##CLIMBSPEED", "gClimbSpeed", 0, 12, "");
EnhancementCheckbox("Faster Block Push", "gFasterBlockPush");
EnhancementCheckbox("No Forced Navi", "gNoForcedNavi");
Tooltip("Prevent forced Navi conversations");
EnhancementCheckbox("No Skulltula Freeze", "gSkulltulaFreeze");
Tooltip("Stops the game from freezing the player when picking up Gold Skulltulas");
Tooltip("Stops the game from freezing the player\nwhen picking up Gold Skulltulas");
EnhancementCheckbox("MM Bunny Hood", "gMMBunnyHood");
Tooltip("Wearing the Bunny Hood grants a speed increase like in Majora's Mask");
Tooltip("Wearing the Bunny Hood grants a speed\nincrease like in Majora's Mask");
EnhancementCheckbox("Fast Chests", "gFastChests");
Tooltip("Kick open every chest");
EnhancementCheckbox("Fast Drops", "gFastDrops");
Tooltip("Skip first-time pickup messages for consumable items");
EnhancementCheckbox("Better Owl", "gBetterOwl");
Tooltip("The default response to Kaepora Gaebora is always that you understood what he said");
Tooltip("The default response to Kaepora Gaebora is\nalways that you understood what he said");
EnhancementCheckbox("Fast Ocarina Playback", "gFastOcarinaPlayback");
Tooltip("Skip the part where the Ocarina playback is called when you play\na song");
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Difficulty Options"))
{
EnhancementSliderInt("Damage Multiplier %dx", "##DAMAGEMUL", "gDamageMul", 1, 4, "");
Tooltip("Modifies all sources of damage not affected by other sliders");
EnhancementSliderInt("Fall Damage Multiplier %dx", "##FALLDAMAGEMUL", "gFallDamageMul", 1, 4, "");
Tooltip("Modifies all fall damage");
EnhancementSliderInt("Void Damage Multiplier %dx", "##VOIDDAMAGEMUL", "gVoidDamageMul", 1, 4, "");
Tooltip("Modifies all void out damage");
ImGui::Text("Damage Multiplier");
EnhancementCombobox("gDamageMul", powers, 9, 0);
Tooltip("Modifies all sources of damage not affected by other sliders\n\
2x: Can survive all common attacks from the start of the game\n\
4x: Dies in 1 hit to any substantial attack from the start of the game\n\
8x: Can only survive trivial damage from the start of the game\n\
16x: Can survive all common attacks with max health without double defense\n\
32x: Can survive all common attacks with max health and double defense\n\
64x: Can survive trivial damage with max health without double defense\n\
128x: Can survive trivial damage with max health and double defense\n\
256x: Cannot survive damage");
ImGui::Text("Fall Damage Multiplier");
EnhancementCombobox("gFallDamageMul", powers, 8, 0);
Tooltip("Modifies all fall damage\n\
2x: Can survive all fall damage from the start of the game\n\
4x: Can only survive short fall damage from the start of the game\n\
8x: Cannot survive any fall damage from the start of the game\n\
16x: Can survive all fall damage with max health without double defense\n\
32x: Can survive all fall damage with max health and double defense\n\
64x: Can survive short fall damage with double defense\n\
128x: Cannot survive fall damage");
ImGui::Text("Void Damage Multiplier");
EnhancementCombobox("gVoidDamageMul", powers, 7, 0);
Tooltip("Modifies damage taken after falling into a void\n\
2x: Can survive void damage from the start of the game\n\
4x: Cannot survive void damage from the start of the game\n\
8x: Can survive void damage twice with max health without double defense\n\
16x: Can survive void damage with max health without double defense\n\
32x: Can survive void damage with max health and double defense\n\
64x: Cannot survive void damage");
EnhancementCheckbox("No Random Drops", "gNoRandomDrops");
Tooltip("Disables random drops, except from the Goron Pot, Dampe, and bosses");
EnhancementCheckbox("No Heart Drops", "gNoHeartDrops");
Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off. This simulates Hero Mode from other games in the series.");
Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off\nThis simulates Hero Mode from other games in the series");
if (ImGui::BeginMenu("Potion Values"))
{
EnhancementCheckbox("Change Red Potion Effect", "gRedPotionEffect");
@ -954,7 +919,7 @@ namespace SohImGui {
Tooltip("Changes the amount of health restored by Red Potions");
EnhancementCheckbox("Red Potion Percent Restore", "gRedPercentRestore");
Tooltip("Toggles from Red Potions restoring a fixed amount of health to a percent of the player's current max health");
EnhancementCheckbox("Change Green Potion Effect", "gGreenPotionEffect");
Tooltip("Enable the following changes to the amount of mana restored by Green Potions");
EnhancementSliderInt("Green Potion Mana: %d", "##GREENPOTIONMANA", "gGreenPotionMana", 1, 100, "");
@ -968,7 +933,7 @@ namespace SohImGui {
Tooltip("Changes the amount of health restored by Blue Potions");
EnhancementCheckbox("Blue Potion Health Percent Restore", "gBlueHealthPercentRestore");
Tooltip("Toggles from Blue Potions restoring a fixed amount of health to a percent of the player's current max health");
EnhancementSliderInt("Blue Potion Mana: %d", "##BLUEPOTIONMANA", "gBluePotionMana", 1, 100, "");
Tooltip("Changes the amount of mana restored by Blue Potions, base max mana is 48, max upgraded mana is 96");
EnhancementCheckbox("Blue Potion Mana Percent Restore", "gBlueManaPercentRestore");
@ -982,7 +947,7 @@ namespace SohImGui {
Tooltip("Toggles from Milk restoring a fixed amount of health to a percent of the player's current max health");
EnhancementCheckbox("Separate Half Milk Effect", "gSeparateHalfMilkEffect");
Tooltip("Enable the following changes to the amount of health restored by Half Milk.\nIf this is disabled, Half Milk will behave the same as Full Milk.");
Tooltip("Enable the following changes to the amount of health restored by Half Milk\nIf this is disabled, Half Milk will behave the same as Full Milk.");
EnhancementSliderInt("Half Milk Health: %d", "##HALFMILKHEALTH", "gHalfMilkHealth", 1, 100, "");
Tooltip("Changes the amount of health restored by Half Milk");
EnhancementCheckbox("Half Milk Percent Restore", "gHalfMilkPercentRestore");
@ -1004,7 +969,19 @@ namespace SohImGui {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Fishing")) {
EnhancementCheckbox("Instant Fishing", "gInstantFishing");
Tooltip("All fish will be caught instantly");
EnhancementCheckbox("Guarantee Bite", "gGuaranteeFishingBite");
Tooltip("When a line is stable, guarantee bite. Otherwise use default logic");
EnhancementSliderInt("Child Minimum Weight: %d", "##cMinimumWeight", "gChildMinimumWeightFish", 6, 10, "", 10);
Tooltip("The minimum weight for the unique fishing reward as a child");
EnhancementSliderInt("Adult Minimum Weight: %d", "##aMinimumWeight", "gAdultMinimumWeightFish", 8, 13, "", 13);
Tooltip("The minimum weight for the unique fishing reward as an adult");
ImGui::EndMenu();
}
ImGui::EndMenu();
}
@ -1013,23 +990,27 @@ namespace SohImGui {
EnhancementCheckbox("Mute Low HP Alarm", "gLowHpAlarm");
Tooltip("Disable the low HP beeping sound");
EnhancementCheckbox("Minimal UI", "gMinimalUI");
Tooltip("Hides most of the UI when not needed");
Tooltip("Hides most of the UI when not needed\nNote: Doesn't activate until after loading a new scene");
EnhancementCheckbox("Disable Navi Call Audio", "gDisableNaviCallAudio");
Tooltip("Disables the voice audio when Navi calls you");
ImGui::EndMenu();
}
EnhancementCheckbox("Visual Stone of Agony", "gVisualAgony");
Tooltip("Displays an icon and plays a sound when Stone of Agony should be activated, for those without rumble");
Tooltip("Displays an icon and plays a sound when Stone of Agony\nshould be activated, for those without rumble");
EnhancementCheckbox("Assignable Tunics and Boots", "gAssignableTunicsAndBoots");
Tooltip("Allows equipping the tunic and boots to c-buttons");
EnhancementCheckbox("Link's Cow in Both Time Periods", "gCowOfTime");
Tooltip("Allows the Lon Lon Ranch obstacle course reward to be shared across time periods");
Tooltip("Allows the Lon Lon Ranch obstacle course reward to be\nshared across time periods");
EnhancementCheckbox("Enable visible guard vision", "gGuardVision");
EnhancementCheckbox("Enable passage of time on file select", "gTimeFlowFileSelect");
EnhancementCheckbox("Allow the cursor to be on any slot", "gPauseAnyCursor");
Tooltip("Allows the cursor on the pause menu to be over any slot. Similar to Rando and Spaceworld 97");
Tooltip("Allows the cursor on the pause menu to be over any slot\nSimilar to Rando and Spaceworld 97");
EnhancementCheckbox("Count Golden Skulltulas", "gInjectSkulltulaCount");
Tooltip("Injects Golden Skulltula total count in pickup messages");
EnhancementCheckbox("Pull grave during the day", "gDayGravePull");
Tooltip("Allows graves to be pulled when child during the day");
ImGui::EndMenu();
}
@ -1078,9 +1059,9 @@ namespace SohImGui {
EnhancementCheckbox("N64 Mode", "gN64Mode");
Tooltip("Sets aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution");
EnhancementCheckbox("Enable 3D Dropped items/projectiles", "gNewDrops");
Tooltip("Change most 2D items & projectiles to their a 3D version");
Tooltip("Change most 2D items and projectiles on the overworld to their 3D versions");
EnhancementCheckbox("Disable Black Bar Letterboxes", "gDisableBlackBars");
Tooltip("Disables Black Bar Letterboxes during cutscenes and Z-targeting\nNote: there may be minor visual glitches that were covered up by the black bars\nPlease disable this setting before reporting a bug");
Tooltip("Disables Black Bar Letterboxes during cutscenes and Z-targeting\nNote: there may be minor visual glitches that\nwere covered up by the black bars\nPlease disable this setting before reporting a bug");
EnhancementCheckbox("Dynamic Wallet Icon", "gDynamicWalletIcon");
Tooltip("Changes the rupee in the wallet icon to match the wallet size you currently have");
EnhancementCheckbox("Always show dungeon entrances", "gAlwaysShowDungeonMinimapIcon");
@ -1094,19 +1075,19 @@ namespace SohImGui {
EnhancementCheckbox("Fix L&R Pause menu", "gUniformLR");
Tooltip("Makes the L and R buttons in the pause menu the same color");
EnhancementCheckbox("Fix L&Z Page switch in Pause menu", "gNGCKaleidoSwitcher");
Tooltip("Enabling it make L and R be your page switch like on Gamecube\nZ become the button to open Debug Menu");
Tooltip("Makes L and R switch pages like on the GameCube\nZ opens the Debug Menu instead");
EnhancementCheckbox("Fix Dungeon entrances", "gFixDungeonMinimapIcon");
Tooltip("Show dungeon entrances icon only when it should be");
Tooltip("Removes the dungeon entrance icon on the top-left corner\nof the screen when no dungeon is present on the current map");
EnhancementCheckbox("Fix Two Handed idle animations", "gTwoHandedIdle");
Tooltip("Makes two handed idle animation play, a seemingly finished animation that was disabled on accident in the original game");
Tooltip("Re-enables the two-handed idle animation, a seemingly\nfinished animation that was disabled on accident in the original game");
EnhancementCheckbox("Fix the Gravedigging Tour Glitch", "gGravediggingTourFix");
Tooltip("Fixes a bug where you can permanently miss the Gravedigging Tour Heart Piece");
Tooltip("Fixes a bug where the Gravedigging Tour Heart\nPiece disappears if the area reloads");
EnhancementCheckbox("Fix Deku Nut upgrade", "gDekuNutUpgradeFix");
Tooltip("Prevents the Forest Stage Deku Nut upgrade from becoming unobtainable after receiving the Poacher's Saw");
Tooltip("Prevents the Forest Stage Deku Nut upgrade from\nbecoming unobtainable after receiving the Poacher's Saw");
EnhancementCheckbox("Fix Navi text HUD position", "gNaviTextFix");
Tooltip("Correctly centers the Navi text prompt on the HUD's C-Up button");
EnhancementCheckbox("Fix Anubis fireballs", "gAnubisFix");
Tooltip("Make Anubis fireballs do fire damage when reflected back at them with the Mirror Shield");
Tooltip("Make Anubis fireballs do fire damage when reflected\nback at them with the Mirror Shield");
ImGui::EndMenu();
}
@ -1116,11 +1097,11 @@ namespace SohImGui {
EnhancementCheckbox("Red Ganon blood", "gRedGanonBlood");
Tooltip("Restore the original red blood from NTSC 1.0/1.1. Disable for green blood");
EnhancementCheckbox("Fish while hovering", "gHoverFishing");
Tooltip("Restore a bug from NTSC 1.0 that allows casting the Fishing Rod while using the Hover Boots");
Tooltip("Restore a bug from NTSC 1.0 that allows casting\nthe Fishing Rod while using the Hover Boots");
EnhancementCheckbox("N64 Weird Frames", "gN64WeirdFrames");
Tooltip("Restores N64 Weird Frames allowing weirdshots to behave the same as N64");
EnhancementCheckbox("Bombchus out of bounds", "gBombchusOOB");
Tooltip("Allows bombchus to explode out of bounds similar to GameCube and Wii VC");
Tooltip("Allows bombchus to explode out of bounds\nSimilar to GameCube and Wii VC");
ImGui::EndMenu();
}
@ -1148,12 +1129,12 @@ namespace SohImGui {
needs_save = true;
}
Tooltip("Interpolate extra frames to get smoother graphics.\n"
"Set to match your monitor's refresh rate, or a divisor of it.\n"
Tooltip("Interpolate extra frames to get smoother graphics\n"
"Set to match your monitor's refresh rate, or a divisor of it\n"
"A higher target FPS than your monitor's refresh rate will just waste resources,\n"
"and might give a worse result.\n"
"For consistent input lag, set this value and your monitor's refresh rate to a multiple of 20.\n"
"Ctrl+Click for keyboard input.");
"For consistent input lag, set this value and your monitor's refresh rate to a multiple of 20\n"
"Ctrl+Click for keyboard input");
}
if (impl.backend == Backend::DX11)
{
@ -1168,21 +1149,25 @@ namespace SohImGui {
}
}
EnhancementCheckbox("Disable LOD", "gDisableLOD");
Tooltip("Turns off the level of detail setting, making models always use their higher poly variants");
Tooltip("Turns off the Level of Detail setting, making models use their higher-poly variants at any distance");
EnhancementCheckbox("Disable Draw Distance", "gDisableDrawDistance");
Tooltip("Turns off the objects draw distance, making objects being visible from a longer range");
Tooltip("Turns off the objects draw distance,\nmaking objects being visible from a longer range");
if (CVar_GetS32("gDisableDrawDistance", 0) == 0) {
CVar_SetS32("gDisableKokiriDrawDistance", 0);
} else if (CVar_GetS32("gDisableDrawDistance", 0) == 1) {
EnhancementCheckbox("Kokiri Draw Distance", "gDisableKokiriDrawDistance");
Tooltip("Kokiris are mystical being that appear from a certain distance\nEnable this will remove their draw distance");
Tooltip("The Kokiri are mystical beings that fade into view when approached\nEnabling this will remove their draw distance");
}
EnhancementCheckbox("Skip Text", "gSkipText");
Tooltip("Holding down B skips text.\nKnown to cause a cutscene softlock in Water Temple.\nSoftlock can be fixed by pressing D-Right in Debug mode.");
Tooltip("Holding down B skips text\nKnown to cause a cutscene softlock in Water Temple\nSoftlock can be fixed by pressing D-Right in Debug mode");
EnhancementCheckbox("Free Camera", "gFreeCamera");
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Cheats"))
{
if (ImGui::BeginMenu("Infinite...")) {
@ -1205,7 +1190,7 @@ namespace SohImGui {
EnhancementCheckbox("Super Tunic", "gSuperTunic");
Tooltip("Makes every tunic have the effects of every other tunic");
EnhancementCheckbox("Easy ISG", "gEzISG");
Tooltip("Automatically activates the Infinite Sword glitch, making you constantly swing your sword");
Tooltip("Passive Infinite Sword Glitch\nIt makes your sword's swing effect and hitbox stay active indefinitely");
EnhancementCheckbox("Unrestricted Items", "gNoRestrictItems");
Tooltip("Allows you to use any item at any location");
EnhancementCheckbox("Freeze Time", "gFreezeTime");
@ -1215,35 +1200,38 @@ namespace SohImGui {
EnhancementCheckbox("Fireproof Deku Shield", "gFireproofDekuShield");
Tooltip("Prevents the Deku Shield from burning on contact with fire");
EnhancementCheckbox("Shield with Two-Handed Weapons", "gShieldTwoHanded");
Tooltip("Allows Link to shield normally with two-handed swords and the Megaton Hammer");
Tooltip("This allows you to put up your shield with any two-handed weapon in hand\nexcept for Deku Sticks");
ImGui::EndMenu();
}
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu("Developer Tools"))
{
EnhancementCheckbox("OoT Debug Mode", "gDebugEnabled");
Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip with L + D-pad Right,\nand open the debug menu with L on the pause screen");
EnhancementCheckbox("Fast File Select", "gSkipLogoTitle");
Tooltip("Directly load the game to selected slot bellow\nUse slot number 4 to load directly in Zelda Map Select\n(Do not require debug menu but you will be unable to save there)\n(you can also load Zelda map select with Debug mod + slot 0).\nWith Slot : 0 you can go directly in File Select menu\nAttention, Loading an empty save will result in crash");
Tooltip("Load the game to the selected slot below upon launch\nUse slot number 4 to load directly into the game's internal Map Select\n(Does not require the Debug Menu, but you will be unable to save there\nYou can also load the Map Select with OoT Debug Mode + slot 0)\nWith slot 0 you can directly go to the File Select menu\nAttention: loading an empty save file will result in a crash");
if (CVar_GetS32("gSkipLogoTitle", 0)) {
EnhancementSliderInt("Loading %d", "##SaveFileID", "gSaveFileID", 0, 4, "");
}
ImGui::Separator();
EnhancementCheckbox("Stats", "gStatsEnabled");
Tooltip("Shows the stats window, with your FPS and frametimes, and the OS you're playing on");
Tooltip("Shows the stats window, with your FPS and frametimes,\nand the OS you're playing on");
EnhancementCheckbox("Console", "gConsoleEnabled");
Tooltip("Enables the console window, allowing you to input commands, type help for some examples");
Tooltip("Enables the console window, allowing you to input commands,\ntype help for some examples");
console->opened = CVar_GetS32("gConsoleEnabled", 0);
ImGui::EndMenu();
}
for (const auto& category : windowCategories) {
ImGui::SetCursorPosY(0.0f);
if (ImGui::BeginMenu(category.first.c_str())) {
for (const std::string& name : category.second) {
std::string varName(name);
varName.erase(std::ranges::remove_if(varName, isspace).begin(), varName.end());
varName.erase(std::remove_if(varName.begin(), varName.end(), [](unsigned char x) { return std::isspace(x); }), varName.end());
std::string toggleName = "g" + varName + "Enabled";
EnhancementCheckbox(name.c_str(), toggleName.c_str());
@ -1251,7 +1239,6 @@ namespace SohImGui {
}
ImGui::EndMenu();
}
}
ImGui::EndMenuBar();
@ -1259,6 +1246,16 @@ namespace SohImGui {
ImGui::End();
for (const auto& category : hiddenwindowCategories) {
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::SetNextWindowSize(ImVec2 (0,0));
ImGuiWindowFlags HiddenWndFlags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavInputs |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoDecoration;
ImGui::Begin(category.first.c_str(), nullptr, HiddenWndFlags);
ImGui::End();
ImGui::PopStyleColor();
}
if (CVar_GetS32("gStatsEnabled", 0)) {
const float framerate = ImGui::GetIO().Framerate;
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
@ -1277,6 +1274,7 @@ namespace SohImGui {
}
console->Draw();
controller->DrawHud();
for (auto& windowIter : customWindows) {
CustomWindow& window = windowIter.second;
@ -1290,7 +1288,7 @@ namespace SohImGui {
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground;
ImGui::Begin("OoT Master Quest", nullptr, flags);
ImGui::Begin("Main Game", nullptr, flags);
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
@ -1397,8 +1395,6 @@ namespace SohImGui {
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
//Placed here so it does the rainbow effects even if menu is not on.
LoadRainbowColor();
}
void CancelFrame() {
@ -1412,18 +1408,22 @@ namespace SohImGui {
console->Commands[cmd] = std::move(entry);
}
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc) {
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc, bool isEnabled, bool isHidden) {
if (customWindows.contains(name)) {
SPDLOG_ERROR("SohImGui::AddWindow: Attempting to add duplicate window name %s", name.c_str());
return;
}
customWindows[name] = {
.enabled = false,
.enabled = isEnabled,
.drawFunc = drawFunc
};
windowCategories[category].emplace_back(name);
if (isHidden) {
hiddenwindowCategories[category].emplace_back(name);
} else {
windowCategories[category].emplace_back(name);
}
}
ImTextureID GetTextureByName(const std::string& name) {
@ -1440,4 +1440,124 @@ namespace SohImGui {
#endif
return reinterpret_cast<ImTextureID>(id);
}
void BeginGroupPanel(const char* name, const ImVec2& size)
{
ImGui::BeginGroup();
// auto cursorPos = ImGui::GetCursorScreenPos();
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
auto frameHeight = ImGui::GetFrameHeight();
ImGui::BeginGroup();
ImVec2 effectiveSize = size;
if (size.x < 0.0f)
effectiveSize.x = ImGui::GetContentRegionAvail().x;
else
effectiveSize.x = size.x;
ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f));
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::SameLine(0.0f, 0.0f);
ImGui::BeginGroup();
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::SameLine(0.0f, 0.0f);
ImGui::TextUnformatted(name);
auto labelMin = ImGui::GetItemRectMin();
auto labelMax = ImGui::GetItemRectMax();
ImGui::SameLine(0.0f, 0.0f);
ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y));
ImGui::BeginGroup();
//ImGui::GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255));
ImGui::PopStyleVar(2);
#if IMGUI_VERSION_NUM >= 17301
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f;
ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f;
ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f;
#else
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f;
#endif
ImGui::GetCurrentWindow()->Size.x -= frameHeight;
auto itemWidth = ImGui::CalcItemWidth();
ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight));
s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax));
}
void EndGroupPanel(float minHeight) {
ImGui::PopItemWidth();
auto itemSpacing = ImGui::GetStyle().ItemSpacing;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
auto frameHeight = ImGui::GetFrameHeight();
ImGui::EndGroup();
//ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f);
ImGui::EndGroup();
ImGui::SameLine(0.0f, 0.0f);
ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f));
ImGui::Dummy(ImVec2(0.0, std::max(frameHeight - frameHeight * 0.5f - itemSpacing.y, minHeight)));
ImGui::EndGroup();
auto itemMin = ImGui::GetItemRectMin();
auto itemMax = ImGui::GetItemRectMax();
//ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f);
auto labelRect = s_GroupPanelLabelStack.back();
s_GroupPanelLabelStack.pop_back();
ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f;
ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f));
labelRect.Min.x -= itemSpacing.x;
labelRect.Max.x += itemSpacing.x;
for (int i = 0; i < 4; ++i)
{
switch (i)
{
// left half-plane
case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break;
// right half-plane
case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break;
// top
case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break;
// bottom
case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break;
}
ImGui::GetWindowDrawList()->AddRect(
frameRect.Min, frameRect.Max,
ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)),
halfFrame.x);
ImGui::PopClipRect();
}
ImGui::PopStyleVar(2);
#if IMGUI_VERSION_NUM >= 17301
ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f;
ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f;
ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f;
#else
ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f;
#endif
ImGui::GetCurrentWindow()->Size.x += frameHeight;
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::EndGroup();
}
}

View File

@ -3,6 +3,7 @@
#include "GameOverlay.h"
#include "Lib/ImGui/imgui.h"
#include "Console.h"
#include "InputEditor.h"
struct GameAsset {
uint32_t textureId;
@ -59,34 +60,38 @@ namespace SohImGui {
} CustomWindow;
extern Console* console;
extern Ship::InputEditor* controller;
extern Ship::GameOverlay* overlay;
extern bool needs_save;
void Init(WindowImpl window_impl);
void Update(EventImpl event);
void Tooltip(const char* text);
void EnhancementRadioButton(const char* text, const char* cvarName, int id);
void EnhancementCheckbox(const char* text, const char* cvarName);
void EnhancementButton(const char* text, const char* cvarName);
void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format);
void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format, int defaultValue = 0);
void EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage);
void EnhancementCombobox(const char* name, const char* ComboArray[], size_t arraySize, uint8_t FirstTimeValue);
void EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha=false, bool TitleSameLine=false);
void EnhancementCombo(const std::string& name, const char* cvarName, const std::vector<std::string>& items, int defaultValue = 0);
void DrawMainMenuAndCalculateGameSize(void);
void DrawFramebufferAndGameInput(void);
void Render(void);
void CancelFrame(void);
void ShowCursor(bool hide, Dialogues w);
void BindCmd(const std::string& cmd, CommandEntry entry);
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc);
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc, bool isEnabled=false, bool isHidden=false);
void LoadResource(const std::string& name, const std::string& path, const ImVec4& tint = ImVec4(1, 1, 1, 1));
void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha=false);
int ClampFloatToInt(float value, int min, int max);
void RandomizeColor(const char* cvarName, ImVec4* colors);
void RainbowColor(const char* cvarName, ImVec4* colors);
void ResetColor(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha);
ImTextureID GetTextureByID(int id);
ImTextureID GetTextureByName(const std::string& name);
void BeginGroupPanel(const char* name, const ImVec2 & size = ImVec2(0.0f, 0.0f));
void EndGroupPanel(float minHeight = 0.0f);
}

View File

@ -0,0 +1,277 @@
#include "InputEditor.h"
#include "Controller.h"
#include "Window.h"
#include "Lib/ImGui/imgui.h"
#include "ImGuiImpl.h"
#include "Utils/StringHelper.h"
#include "Lib/ImGui/imgui_internal.h"
namespace Ship {
extern "C" uint8_t __enableGameInput;
#define SEPARATION() ImGui::Dummy(ImVec2(0, 5))
void InputEditor::Init() {
BtnReading = -1;
}
std::shared_ptr<Controller> GetControllerPerSlot(int slot) {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
return Window::ControllerApi->physicalDevices[vDevices[slot]];
}
void InputEditor::DrawButton(const char* label, int n64Btn) {
const std::shared_ptr<Controller> backend = GetControllerPerSlot(CurrentPort);
float size = 40;
bool readingMode = BtnReading == n64Btn;
bool disabled = BtnReading != -1 && !readingMode || !backend->Connected();
ImVec2 len = ImGui::CalcTextSize(label);
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(pos.y + len.y / 4);
ImGui::SetCursorPosX(pos.x + abs(len.x - size));
ImGui::Text("%s", label);
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
if(disabled) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
}
if(readingMode) {
const int32_t btn = backend->ReadRawPress();
if(btn != -1) {
backend->SetButtonMapping(CurrentPort, n64Btn, btn);
BtnReading = -1;
}
}
const char* BtnName = backend->GetButtonName(CurrentPort, n64Btn);
if (ImGui::Button(StringHelper::Sprintf("%s##HBTNID_%d", readingMode ? "Press a Key..." : BtnName, n64Btn).c_str())) {
BtnReading = n64Btn;
backend->ClearRawPress();
}
if(disabled) {
ImGui::PopItemFlag();
ImGui::PopStyleVar();
}
}
void InputEditor::DrawVirtualStick(const char* label, ImVec2 stick) {
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y));
ImGui::BeginChild(label, ImVec2(68, 75), false);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
float sz = 45.0f;
float rad = sz * 0.5f;
ImVec2 pos = ImVec2(p.x + sz * 0.5f + 12, p.y + sz * 0.5f + 11);
float stickX = (stick.x / 83.0f) * (rad * 0.5f);
float stickY = -(stick.y / 83.0f) * (rad * 0.5f);
ImVec4 rect = ImVec4(p.x + 2, p.y + 2, 65, 65);
draw_list->AddRect(ImVec2(rect.x, rect.y), ImVec2(rect.x + rect.z, rect.y + rect.w), ImColor(100, 100, 100, 255), 0.0f, 0, 1.5f);
draw_list->AddCircleFilled(pos, rad, ImColor(130, 130, 130, 255), 8);
draw_list->AddCircleFilled(ImVec2(pos.x + stickX, pos.y + stickY), 5, ImColor(15, 15, 15, 255), 7);
ImGui::EndChild();
}
void InputEditor::DrawControllerSchema() {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
const std::vector<std::shared_ptr<Controller>> devices = Window::ControllerApi->physicalDevices;
std::shared_ptr<Controller> Backend = devices[vDevices[CurrentPort]];
DeviceProfile& profile =Backend->profiles[CurrentPort];
float sensitivity = profile.Thresholds[SENSITIVITY];
bool IsKeyboard = Backend->GetGuid() == "Keyboard" || !Backend->Connected();
const char* ControllerName = Backend->GetControllerName();
if (ControllerName != nullptr && ImGui::BeginCombo("##ControllerEntries", ControllerName)) {
for (uint8_t i = 0; i < devices.size(); i++) {
if (ImGui::Selectable(devices[i]->GetControllerName(), i == vDevices[CurrentPort])) {
Window::ControllerApi->SetPhysicalDevice(CurrentPort, i);
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if(ImGui::Button("Refresh")) {
Window::ControllerApi->ScanPhysicalDevices();
}
SohImGui::BeginGroupPanel("Buttons", ImVec2(150, 20));
DrawButton("A", BTN_A);
DrawButton("B", BTN_B);
DrawButton("L", BTN_L);
DrawButton("R", BTN_R);
DrawButton("Z", BTN_Z);
DrawButton("START", BTN_START);
SEPARATION();
SohImGui::EndGroupPanel(IsKeyboard ? 7.0f : 48.0f);
ImGui::SameLine();
SohImGui::BeginGroupPanel("Digital Pad", ImVec2(150, 20));
DrawButton("Up", BTN_DUP);
DrawButton("Down", BTN_DDOWN);
DrawButton("Left", BTN_DLEFT);
DrawButton("Right", BTN_DRIGHT);
SEPARATION();
SohImGui::EndGroupPanel(IsKeyboard ? 53.0f : 94.0f);
ImGui::SameLine();
SohImGui::BeginGroupPanel("Analog Stick", ImVec2(150, 20));
DrawButton("Up", BTN_STICKUP);
DrawButton("Down", BTN_STICKDOWN);
DrawButton("Left", BTN_STICKLEFT);
DrawButton("Right", BTN_STICKRIGHT);
if (!IsKeyboard) {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
DrawVirtualStick("##MainVirtualStick", ImVec2(Backend->wStickX, Backend->wStickY));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##MSInput", ImVec2(90, 50), false);
ImGui::Text("Deadzone");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MDZone", &profile.Thresholds[LEFT_STICK]);
ImGui::PopItemWidth();
ImGui::EndChild();
} else {
ImGui::Dummy(ImVec2(0, 6));
}
SohImGui::EndGroupPanel(IsKeyboard ? 52.0f : 24.0f);
ImGui::SameLine();
if (!IsKeyboard) {
ImGui::SameLine();
SohImGui::BeginGroupPanel("Camera Stick", ImVec2(150, 20));
DrawButton("Up", BTN_VSTICKUP);
DrawButton("Down", BTN_VSTICKDOWN);
DrawButton("Left", BTN_VSTICKLEFT);
DrawButton("Right", BTN_VSTICKRIGHT);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
DrawVirtualStick("##CameraVirtualStick", ImVec2(Backend->wCamX / sensitivity, Backend->wCamY / sensitivity));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##CSInput", ImVec2(90, 85), false);
ImGui::Text("Deadzone");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MDZone", &profile.Thresholds[RIGHT_STICK]);
ImGui::PopItemWidth();
ImGui::Text("Sensitivity");
ImGui::PushItemWidth(80);
ImGui::InputInt("##MSensitivity", &profile.Thresholds[SENSITIVITY]);
ImGui::PopItemWidth();
ImGui::EndChild();
SohImGui::EndGroupPanel(14.0f);
}
if(Backend->CanGyro()) {
ImGui::SameLine();
SohImGui::BeginGroupPanel("Gyro Options", ImVec2(175, 20));
float cursorX = ImGui::GetCursorPosX() + 5;
ImGui::SetCursorPosX(cursorX);
ImGui::Checkbox("Enable Gyro", &profile.UseGyro);
ImGui::SetCursorPosX(cursorX);
ImGui::Text("Gyro Sensitivity: %d%%", profile.Thresholds[GYRO_SENSITIVITY]);
ImGui::PushItemWidth(135.0f);
ImGui::SetCursorPosX(cursorX);
ImGui::SliderInt("##GSensitivity", &profile.Thresholds[GYRO_SENSITIVITY], 0, 100, "");
ImGui::PopItemWidth();
ImGui::Dummy(ImVec2(0, 1));
ImGui::SetCursorPosX(cursorX);
if (ImGui::Button("Recalibrate Gyro##RGyro")) {
profile.Thresholds[DRIFT_X] = 0;
profile.Thresholds[DRIFT_Y] = 0;
}
ImGui::SetCursorPosX(cursorX);
DrawVirtualStick("##GyroPreview", ImVec2(Backend->wGyroX, Backend->wGyroY));
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5);
ImGui::BeginChild("##GyInput", ImVec2(90, 85), false);
ImGui::Text("Drift X");
ImGui::PushItemWidth(80);
ImGui::InputInt("##GDriftX", &profile.Thresholds[DRIFT_X]);
ImGui::PopItemWidth();
ImGui::Text("Drift Y");
ImGui::PushItemWidth(80);
ImGui::InputInt("##GDriftY", &profile.Thresholds[DRIFT_Y]);
ImGui::PopItemWidth();
ImGui::EndChild();
SohImGui::EndGroupPanel(14.0f);
}
ImGui::SameLine();
const ImVec2 cursor = ImGui::GetCursorPos();
SohImGui::BeginGroupPanel("C-Buttons", ImVec2(158, 20));
DrawButton("Up", BTN_CUP);
DrawButton("Down", BTN_CDOWN);
DrawButton("Left", BTN_CLEFT);
DrawButton("Right", BTN_CRIGHT);
ImGui::Dummy(ImVec2(0, 5));
SohImGui::EndGroupPanel();
ImGui::SetCursorPosX(cursor.x);
ImGui::SetCursorPosY(cursor.y + 120);
SohImGui::BeginGroupPanel("Options", ImVec2(158, 20));
float cursorX = ImGui::GetCursorPosX() + 5;
ImGui::SetCursorPosX(cursorX);
ImGui::Checkbox("Rumble Enabled", &profile.UseRumble);
if (Backend->CanRumble()) {
ImGui::SetCursorPosX(cursorX);
ImGui::Text("Rumble Force: %d%%", static_cast<int>(100 * profile.RumbleStrength));
ImGui::SetCursorPosX(cursorX);
ImGui::PushItemWidth(135.0f);
ImGui::SliderFloat("##RStrength", &profile.RumbleStrength, 0, 1.0f, "");
ImGui::PopItemWidth();
}
ImGui::Dummy(ImVec2(0, 5));
SohImGui::EndGroupPanel(IsKeyboard ? 0.0f : 2.0f);
}
void InputEditor::DrawHud() {
__enableGameInput = true;
if (!this->Opened) {
BtnReading = -1;
return;
}
ImGui::SetNextWindowSizeConstraints(ImVec2(641, 250), ImVec2(1200, 290));
//OTRTODO: Disable this stupid workaround ( ReadRawPress() only works when the window is on the main viewport )
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
ImGui::Begin("Controller Configuration", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize);
ImGui::BeginTabBar("##Controllers");
for (int i = 0; i < 4; i++) {
if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d", i + 1).c_str())) {
CurrentPort = i;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
// Draw current cfg
DrawControllerSchema();
ImGui::End();
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include "Lib/ImGui/imgui.h"
namespace Ship {
class InputEditor {
int CurrentPort = 0;
int BtnReading = -1;
public:
bool Opened = false;
void Init();
void DrawButton(const char* label, int n64Btn);
void DrawVirtualStick(const char* label, ImVec2 stick);
void DrawControllerSchema();
void DrawHud();
};
}

View File

@ -1,56 +1,105 @@
#include "KeyboardController.h"
#if __APPLE__
#include <SDL_keyboard.h>
#else
#include <SDL2/SDL_keyboard.h>
#endif
#include "Hooks.h"
#include "GlobalCtx2.h"
#include "Window.h"
namespace Ship {
KeyboardController::KeyboardController(int32_t dwControllerNumber) : Controller(dwControllerNumber) {
LoadBinding();
}
KeyboardController::~KeyboardController() {
KeyboardController::KeyboardController() : Controller(), lastScancode(-1) {
GUID = "Keyboard";
}
bool KeyboardController::PressButton(int32_t dwScancode) {
if (ButtonMapping.contains(dwScancode)) {
dwPressedButtons |= ButtonMapping[dwScancode];
return true;
lastKey = dwScancode;
for (int slot = 0; slot < MAXCONTROLLERS; slot++) {
if (profiles[slot].Mappings.contains(dwScancode)) {
dwPressedButtons[slot] |= profiles[slot].Mappings[dwScancode];
return true;
}
}
return false;
}
bool KeyboardController::ReleaseButton(int32_t dwScancode) {
if (ButtonMapping.contains(dwScancode)) {
dwPressedButtons &= ~ButtonMapping[dwScancode];
return true;
for (int slot = 0; slot < MAXCONTROLLERS; slot++) {
if (profiles[slot].Mappings.contains(dwScancode)) {
dwPressedButtons[slot] &= ~profiles[slot].Mappings[dwScancode];
return true;
}
}
return false;
}
void KeyboardController::ReleaseAllButtons() {
dwPressedButtons = 0;
for(int slot = 0; slot < MAXCONTROLLERS; slot++) {
dwPressedButtons[slot] = 0;
}
}
void KeyboardController::ReadFromSource() {
void KeyboardController::ReadFromSource(int32_t slot) {
wStickX = 0;
wStickY = 0;
wCamX = 0;
wCamY = 0;
}
void KeyboardController::WriteToSource(ControllerCallback* controller)
int32_t KeyboardController::ReadRawPress() {
return lastKey;
}
void KeyboardController::WriteToSource(int32_t slot, ControllerCallback* controller)
{
}
std::string KeyboardController::GetControllerType() {
return "KEYBOARD";
const char* KeyboardController::GetButtonName(int slot, int n64Button) {
std::map<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair<int32_t, int32_t>& pair) {
return pair.second == n64Button;
});
if (find == Mappings.end()) return "Unknown";
const char* name = GlobalCtx2::GetInstance()->GetWindow()->GetKeyName(find->first);
return strlen(name) == 0 ? "Unknown" : name;
}
std::string KeyboardController::GetConfSection() {
return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1);
void KeyboardController::CreateDefaultBinding(int32_t slot) {
DeviceProfile& profile = profiles[slot];
profile.Mappings[0x14D] = BTN_CRIGHT;
profile.Mappings[0x14B] = BTN_CLEFT;
profile.Mappings[0x150] = BTN_CDOWN;
profile.Mappings[0x148] = BTN_CUP;
profile.Mappings[0x13] = BTN_R;
profile.Mappings[0x12] = BTN_L;
profile.Mappings[0x023] = BTN_DRIGHT;
profile.Mappings[0x021] = BTN_DLEFT;
profile.Mappings[0x022] = BTN_DDOWN;
profile.Mappings[0x014] = BTN_DUP;
profile.Mappings[0x039] = BTN_START;
profile.Mappings[0x02C] = BTN_Z;
profile.Mappings[0x02E] = BTN_B;
profile.Mappings[0x02D] = BTN_A;
profile.Mappings[0x020] = BTN_STICKRIGHT;
profile.Mappings[0x01E] = BTN_STICKLEFT;
profile.Mappings[0x01F] = BTN_STICKDOWN;
profile.Mappings[0x011] = BTN_STICKUP;
}
std::string KeyboardController::GetBindingConfSection() {
return GetControllerType() + " CONTROLLER BINDING " + std::to_string(GetControllerNumber() + 1);
const char* KeyboardController::GetControllerName() {
return "Keyboard";
}
}

View File

@ -5,24 +5,34 @@
namespace Ship {
class KeyboardController : public Controller {
public:
KeyboardController(int32_t dwControllerNumber);
~KeyboardController();
void ReadFromSource();
void WriteToSource(ControllerCallback* controller);
bool Connected() const { return true; }
bool CanRumble() const { return false; }
KeyboardController();
void ReadFromSource(int32_t slot) override;
void WriteToSource(int32_t slot, ControllerCallback* controller) override;
bool Connected() const override { return true; }
bool CanRumble() const override { return false; }
bool CanGyro() const override { return false; }
const char* GetControllerName() override;
const char* GetButtonName(int slot, int n64Button) override;
bool PressButton(int32_t dwScancode);
bool ReleaseButton(int32_t dwScancode);
void ClearRawPress() override {
lastKey = -1;
}
int32_t ReadRawPress() override;
void ReleaseAllButtons();
bool HasPadConf() const { return false; }
std::optional<std::string> GetPadConfSection() { return {}; }
void SetLastScancode(int32_t key) {
lastScancode = key;
}
int32_t GetLastScancode() { return lastScancode; }
void CreateDefaultBinding(int32_t slot) override;
protected:
std::string GetControllerType();
std::string GetConfSection();
std::string GetBindingConfSection();
int32_t lastScancode;
int32_t lastKey = -1;
};
}

View File

@ -112,9 +112,11 @@ typedef struct {
/* 0x02 */ s8 stick_x;
/* 0x03 */ s8 stick_y;
/* 0x04 */ u8 err_no;
/* 0x05 */ f32 gyro_x;
/* 0x09 */ f32 gyro_y;
} OSContPad; // size = 0x0D
/* 0x05 */ f32 gyro_x;
/* 0x09 */ f32 gyro_y;
/* 0x1C */ f32 cam_x;
/* 0x20 */ f32 cam_y;
} OSContPad; // size = 0x24
typedef struct {
/* 0x00 */ u8 rumble;

View File

@ -137,7 +137,7 @@ static struct {
//uint32_t current_width, current_height;
uint32_t render_target_height;
int current_framebuffer;
FilteringMode current_filter_mode = NONE;
FilteringMode current_filter_mode = FILTER_NONE;
int8_t depth_test;
int8_t depth_mask;
@ -413,7 +413,7 @@ static struct ShaderProgram *gfx_d3d11_create_and_load_new_shader(uint64_t shade
char buf[4096];
size_t len, num_floats;
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, d3d.current_filter_mode == THREE_POINT);
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, d3d.current_filter_mode == FILTER_THREE_POINT);
ComPtr<ID3DBlob> vs, ps;
ComPtr<ID3DBlob> error_blob;
@ -580,7 +580,7 @@ static void gfx_d3d11_set_sampler_parameters(int tile, bool linear_filter, uint3
D3D11_SAMPLER_DESC sampler_desc;
ZeroMemory(&sampler_desc, sizeof(D3D11_SAMPLER_DESC));
sampler_desc.Filter = linear_filter && d3d.current_filter_mode == LINEAR ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
sampler_desc.Filter = linear_filter && d3d.current_filter_mode == FILTER_LINEAR ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
sampler_desc.AddressU = gfx_cm_to_d3d11(cms);
sampler_desc.AddressV = gfx_cm_to_d3d11(cmt);
@ -685,7 +685,7 @@ static void gfx_d3d11_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t
d3d.last_resource_views[i] = d3d.textures[d3d.current_texture_ids[i]].resource_view.Get();
d3d.context->PSSetShaderResources(i, 1, d3d.textures[d3d.current_texture_ids[i]].resource_view.GetAddressOf());
if (d3d.current_filter_mode == THREE_POINT) {
if (d3d.current_filter_mode == FILTER_THREE_POINT) {
d3d.per_draw_cb_data.textures[i].width = d3d.textures[d3d.current_texture_ids[i]].width;
d3d.per_draw_cb_data.textures[i].height = d3d.textures[d3d.current_texture_ids[i]].height;
d3d.per_draw_cb_data.textures[i].linear_filtering = d3d.textures[d3d.current_texture_ids[i]].linear_filtering;

View File

@ -27,9 +27,12 @@
#include "gfx_screen_config.h"
#include "gfx_pc.h"
#include "../../ImGuiImpl.h"
#include "../../Cvar.h"
#include "../../Hooks.h"
#define DECLARE_GFX_DXGI_FUNCTIONS
#include "gfx_dxgi.h"
#include "../../GameSettings.h"
#define WINCLASS_NAME L"N64GAME"
#define GFX_API_NAME "DirectX"
@ -85,8 +88,8 @@ static struct {
static void load_dxgi_library(void) {
dxgi.dxgi_module = LoadLibraryW(L"dxgi.dll");
*(FARPROC *)&dxgi.CreateDXGIFactory1 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory1");
*(FARPROC *)&dxgi.CreateDXGIFactory2 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory2");
*(FARPROC*)&dxgi.CreateDXGIFactory1 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory1");
*(FARPROC*)&dxgi.CreateDXGIFactory2 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory2");
}
template <typename Fun>
@ -110,8 +113,8 @@ static void run_as_dpi_aware(Fun f) {
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext);
*(FARPROC *)&SetThreadDpiAwarenessContext = GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetThreadDpiAwarenessContext");
DPI_AWARENESS_CONTEXT(WINAPI * SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext);
*(FARPROC*)&SetThreadDpiAwarenessContext = GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetThreadDpiAwarenessContext");
DPI_AWARENESS_CONTEXT old_awareness_context = nullptr;
if (SetThreadDpiAwarenessContext != nullptr) {
old_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
@ -121,8 +124,8 @@ static void run_as_dpi_aware(Fun f) {
if (!dxgi.process_dpi_awareness_done) {
HMODULE shcore_module = LoadLibraryW(L"SHCore.dll");
if (shcore_module != nullptr) {
HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);
*(FARPROC *)&SetProcessDpiAwareness = GetProcAddress(shcore_module, "SetProcessDpiAwareness");
HRESULT(WINAPI * SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);
*(FARPROC*)&SetProcessDpiAwareness = GetProcAddress(shcore_module, "SetProcessDpiAwareness");
if (SetProcessDpiAwareness != nullptr) {
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
// Ignore result, will fail if already called or manifest already specifies dpi awareness.
@ -226,6 +229,8 @@ static void onkeyup(WPARAM w_param, LPARAM l_param) {
}
}
char fileName[256];
static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_param, LPARAM l_param) {
SohImGui::EventImpl event_impl;
event_impl.win32 = { h_wnd, static_cast<int>(message), static_cast<int>(w_param), static_cast<int>(l_param) };
@ -236,6 +241,7 @@ static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_par
dxgi.current_height = (uint32_t)(l_param >> 16);
break;
case WM_DESTROY:
ModInternal::ExecuteHooks<ModInternal::ExitGame>();
exit(0);
case WM_PAINT:
if (dxgi.in_paint) {
@ -265,6 +271,12 @@ static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_par
case WM_KEYUP:
onkeyup(w_param, l_param);
break;
case WM_DROPFILES:
DragQueryFileA((HDROP)w_param, 0, fileName, 256);
CVar_SetString("gDroppedFile", fileName);
CVar_SetS32("gNewFileDropped", 1);
Game::SaveSettings();
break;
case WM_SYSKEYDOWN:
if ((w_param == VK_RETURN) && ((l_param & 1 << 30) == 0)) {
toggle_borderless_window_full_screen(!dxgi.is_full_screen, true);
@ -334,6 +346,8 @@ void gfx_dxgi_init(const char *game_name, bool start_in_fullscreen, uint32_t wid
if (start_in_fullscreen) {
toggle_borderless_window_full_screen(true, false);
}
DragAcceptFiles(dxgi.h_wnd, TRUE);
}
static void gfx_dxgi_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
@ -706,6 +720,12 @@ void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message) {
}
}
const char* gfx_dxgi_get_key_name(int scancode) {
TCHAR* Text = new TCHAR[64];
GetKeyNameText(scancode << 16, Text, 64);
return (char*) Text;
}
extern "C" struct GfxWindowManagerAPI gfx_dxgi_api = {
gfx_dxgi_init,
gfx_dxgi_set_keyboard_callbacks,
@ -722,6 +742,7 @@ extern "C" struct GfxWindowManagerAPI gfx_dxgi_api = {
gfx_dxgi_set_target_fps,
gfx_dxgi_set_maximum_frame_latency,
gfx_dxgi_get_detected_hz,
gfx_dxgi_get_key_name
};
#endif

View File

@ -81,7 +81,7 @@ static uint32_t frame_count;
static vector<Framebuffer> framebuffers;
static size_t current_framebuffer;
static float current_noise_scale;
static FilteringMode current_filter_mode = THREE_POINT;
static FilteringMode current_filter_mode = FILTER_THREE_POINT;
GLuint pixel_depth_rb, pixel_depth_fb;
size_t pixel_depth_rb_size;
@ -376,7 +376,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
append_line(fs_buf, &fs_len, "}");
}
if (current_filter_mode == THREE_POINT) {
if (current_filter_mode == FILTER_THREE_POINT) {
#if __APPLE__
append_line(fs_buf, &fs_len, "#define TEX_OFFSET(off) texture(tex, texCoord - (off)/texSize)");
#else
@ -638,6 +638,7 @@ static void gfx_opengl_select_texture(int tile, GLuint texture_id) {
glActiveTexture(GL_TEXTURE0 + tile);
glBindTexture(GL_TEXTURE_2D, texture_id);
}
static void gfx_opengl_upload_texture(const uint8_t *rgba32_buf, uint32_t width, uint32_t height) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba32_buf);
}
@ -657,7 +658,7 @@ static uint32_t gfx_cm_to_opengl(uint32_t val) {
}
static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
const GLint filter = linear_filter && current_filter_mode == LINEAR ? GL_LINEAR : GL_NEAREST;
const GLint filter = linear_filter && current_filter_mode == FILTER_LINEAR ? GL_LINEAR : GL_NEAREST;
glActiveTexture(GL_TEXTURE0 + tile);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);

View File

@ -566,7 +566,7 @@ static bool gfx_texture_cache_lookup(int i, int tile) {
static void gfx_texture_cache_delete(const uint8_t* orig_addr)
{
while (gfx_texture_cache.map.bucket_count() > 0) {
TextureCacheKey key = { orig_addr, 0, 0, 0 }; // bucket index only depends on the address
TextureCacheKey key = { orig_addr, {0}, 0, 0 }; // bucket index only depends on the address
size_t bucket = gfx_texture_cache.map.bucket(key);
bool again = false;
for (auto it = gfx_texture_cache.map.begin(bucket); it != gfx_texture_cache.map.end(bucket); ++it) {

View File

@ -23,7 +23,7 @@ struct XYWidthHeight {
};
struct GfxDimensions {
uint32_t internal_mul;
float internal_mul;
uint32_t width, height;
float aspect_ratio;
};

View File

@ -17,9 +17,9 @@ struct GfxClipParameters {
};
enum FilteringMode {
THREE_POINT,
LINEAR,
NONE
FILTER_THREE_POINT,
FILTER_LINEAR,
FILTER_NONE
};
// A hash function used to hash a: pair<float, float>

View File

@ -22,6 +22,8 @@
#endif
#include "../../ImGuiImpl.h"
#include "../../Cvar.h"
#include "../../Hooks.h"
#include "gfx_window_manager_api.h"
#include "gfx_screen_config.h"
@ -29,6 +31,7 @@
#include <WTypesbase.h>
#endif
#include <time.h>
#include "../../GameSettings.h"
#define GFX_API_NAME "SDL2 - OpenGL"
@ -133,6 +136,8 @@ static int target_fps = 60;
static void gfx_sdl_init(const char *game_name, bool start_in_fullscreen, uint32_t width, uint32_t height) {
SDL_Init(SDL_INIT_VIDEO);
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
@ -225,6 +230,15 @@ static int translate_scancode(int scancode) {
}
}
static int untranslate_scancode(int translatedScancode) {
for (int i = 0; i < 512; i++) {
if (inverted_scancode_table[i] == translatedScancode) {
return i;
}
}
return 0;
}
static void gfx_sdl_onkeydown(int scancode) {
int key = translate_scancode(scancode);
if (on_key_down_callback != NULL) {
@ -260,7 +274,13 @@ static void gfx_sdl_handle_events(void) {
SDL_GL_GetDrawableSize(wnd, &window_width, &window_height);
}
break;
case SDL_DROPFILE:
CVar_SetString("gDroppedFile", event.drop.file);
CVar_SetS32("gNewFileDropped", 1);
Game::SaveSettings();
break;
case SDL_QUIT:
ModInternal::ExecuteHooks<ModInternal::ExitGame>();
SDL_Quit(); // bandaid fix for linux window closing issue
exit(0);
}
@ -330,6 +350,10 @@ static float gfx_sdl_get_detected_hz(void) {
return 0;
}
static const char* gfx_sdl_get_key_name(int scancode) {
return SDL_GetScancodeName((SDL_Scancode) untranslate_scancode(scancode));
}
struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_init,
gfx_sdl_set_keyboard_callbacks,
@ -345,7 +369,8 @@ struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_get_time,
gfx_sdl_set_target_fps,
gfx_sdl_set_maximum_frame_latency,
gfx_sdl_get_detected_hz
gfx_sdl_get_detected_hz,
gfx_sdl_get_key_name
};
#endif

View File

@ -20,6 +20,7 @@ struct GfxWindowManagerAPI {
void (*set_target_fps)(int fps);
void (*set_maximum_frame_latency)(int latency);
float (*get_detected_hz)(void);
const char* (*get_key_name)(int scancode);
};
#endif

View File

@ -0,0 +1,134 @@
#include "Mercury.h"
#include <fstream>
#include <sstream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <any>
namespace fs = std::filesystem;
using json = nlohmann::json;
std::unordered_map<std::string, std::any> ramMap;
Mercury::Mercury(std::string path) : path_(std::move(path)) {
this->reload();
}
std::vector<std::string> split(const std::string& s, const char delimiter) {
std::vector<std::string> result;
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delimiter)) {
result.push_back(item);
}
return result;
}
std::string Mercury::formatNestedKey(const std::string& key) {
const std::vector<std::string> dots = split(key, '.');
std::string tmp;
if (dots.size() > 1)
for (const auto& dot : dots) {
tmp += "/" + dot;
}
else
tmp = "/" + dots[0];
return tmp;
}
json Mercury::nested(const std::string& key) {
std::vector<std::string> dots = split(key, '.');
if (!this->vjson.is_object())
return this->vjson;
json gjson = this->vjson.unflatten();
if (dots.size() > 1) {
for (auto& key : dots) {
if (key == "*" || gjson.contains(key))
gjson = gjson[key];
}
return gjson;
}
return gjson[key];
}
std::string Mercury::getString(const std::string& key, const std::string& def) {
json n = this->nested(key);
if (n.is_string() && !n.get<std::string>().empty())
return n;
return def;
}
float Mercury::getFloat(const std::string& key, float def) {
json n = this->nested(key);
if (n.is_number_float())
return n;
return def;
}
bool Mercury::getBool(const std::string& key, bool def) {
json n = this->nested(key);
if (n.is_boolean())
return n;
return def;
}
int Mercury::getInt(const std::string& key, int def) {
json n = this->nested(key);
if (n.is_number_integer())
return n;
return def;
}
bool Mercury::contains(const std::string& key) {
return !this->nested(key).is_null();
}
void Mercury::setString(const std::string& key, const std::string& value) {
this->vjson[formatNestedKey(key)] = value;
}
void Mercury::setFloat(const std::string& key, float value) {
this->vjson[formatNestedKey(key)] = value;
}
void Mercury::setBool(const std::string& key, bool value) {
this->vjson[formatNestedKey(key)] = value;
}
void Mercury::setInt(const std::string& key, int value) {
this->vjson[formatNestedKey(key)] = value;
}
void Mercury::setUInt(const std::string& key, uint32_t value) {
this->vjson[formatNestedKey(key)] = value;
}
void Mercury::erase(const std::string& key) {
this->vjson.erase(formatNestedKey(key));
}
void Mercury::reload() {
if (this->path_ == "None" || !fs::exists(this->path_) || !fs::is_regular_file(this->path_)) {
this->isNewInstance = true;
this->vjson = json::object();
return;
}
std::ifstream ifs(this->path_);
try {
this->rjson = json::parse(ifs);
this->vjson = this->rjson.flatten();
}
catch (...) {
this->vjson = json::object();
}
}
void Mercury::save() const {
std::ofstream file(this->path_);
file << this->vjson.unflatten().dump(4);
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <any>
#include <vector>
#include <string>
#include "../nlohmann/json.hpp"
class Mercury {
protected:
std::string path_;
public:
explicit Mercury(std::string path);
nlohmann::json vjson;
nlohmann::json rjson;
nlohmann::json nested(const std::string& key);
static std::string formatNestedKey(const std::string& key);
std::string getString(const std::string& key, const std::string& def = "");
float getFloat(const std::string& key, float defValue = 0.0f);
bool getBool(const std::string& key, bool defValue = false);
int getInt(const std::string& key, int defValue = 0);
bool contains(const std::string& key);
template< typename T > std::vector<T> getArray(const std::string& key);
void setString(const std::string& key, const std::string& value);
void setFloat(const std::string& key, float value);
void setBool(const std::string& key, bool value);
void setInt(const std::string& key, int value);
void setUInt(const std::string& key, uint32_t value);
void erase(const std::string& key);
void set(const std::string& key, std::any value);
template< typename T > void setArray(const std::string& key, std::vector<T> array);
void reload();
void save() const;
bool isNewInstance = false;
};
template< typename T >
std::vector<T> Mercury::getArray(const std::string& key) {
if (nlohmann::json tmp = this->nested(key); tmp.is_array())
return tmp.get<std::vector<T>>();
return std::vector<T>();
};
template <typename T>
void Mercury::setArray(const std::string& key, std::vector<T> array) {
this->vjson[formatNestedKey(key)] = nlohmann::json(array);
}

View File

@ -0,0 +1,60 @@
//
// OSXFolderManager.h
// libultraship
//
// Created by David Chavez on 28.06.22.
//
#ifndef OSXFolderManager_h
#define OSXFolderManager_h
#include <stdio.h>
namespace Ship {
enum {
NSApplicationDirectory = 1,
NSDemoApplicationDirectory,
NSDeveloperApplicationDirectory,
NSAdminApplicationDirectory,
NSLibraryDirectory,
NSDeveloperDirectory,
NSUserDirectory,
NSDocumentationDirectory,
NSDocumentDirectory,
NSCoreServiceDirectory,
NSAutosavedInformationDirectory = 11,
NSDesktopDirectory = 12,
NSCachesDirectory = 13,
NSApplicationSupportDirectory = 14,
NSDownloadsDirectory = 15,
NSInputMethodsDirectory = 16,
NSMoviesDirectory = 17,
NSMusicDirectory = 18,
NSPicturesDirectory = 19,
NSPrinterDescriptionDirectory = 20,
NSSharedPublicDirectory = 21,
NSPreferencePanesDirectory = 22,
NSApplicationScriptsDirectory = 23,
NSItemReplacementDirectory = 99,
NSAllApplicationsDirectory = 100,
NSAllLibrariesDirectory = 101,
NSTrashDirectory = 102
};
typedef unsigned long SearchPathDirectory;
enum {
NSUserDomainMask = 1, // user's home directory --- place to install user's personal items (~)
NSLocalDomainMask = 2, // local to the current machine --- place to install items available to everyone on this machine (/Library)
NSNetworkDomainMask = 4, // publically available location in the local area network --- place to install items available on the network (/Network)
NSSystemDomainMask = 8, // provided by Apple, unmodifiable (/System)
NSAllDomainsMask = 0x0ffff // all domains: all of the above and future items
};
typedef unsigned long SearchPathDomainMask;
class FolderManager {
public:
const char *pathForDirectory(SearchPathDirectory directory, SearchPathDomainMask domainMask);
const char *pathForDirectoryAppropriateForItemAtPath(SearchPathDirectory directory, SearchPathDomainMask domainMask, const char *itemPath, bool create = false);
};
};
#endif /* OSXFolderManager_h */

View File

@ -0,0 +1,37 @@
//
// OSXFolderManager.m
// libultraship
//
// Created by David Chavez on 28.06.22.
//
#include "OSXFolderManager.h"
#import <Foundation/Foundation.h>
using namespace Ship;
const char * FolderManager::pathForDirectory(SearchPathDirectory directory, SearchPathDomainMask domainMask) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *URLs = [fileManager URLsForDirectory:(NSSearchPathDirectory)directory inDomains:domainMask];
if (URLs.count == 0) return NULL;
NSURL *URL = [URLs objectAtIndex:0];
NSString *path = URL.path;
// `fileSystemRepresentation` on an `NSString` gives a path suitable for POSIX APIs
return path.fileSystemRepresentation;
}
const char * FolderManager::pathForDirectoryAppropriateForItemAtPath(SearchPathDirectory directory,
SearchPathDomainMask domainMask, const char *itemPath, bool create) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *nsPath = [fileManager stringWithFileSystemRepresentation:itemPath length:strlen(itemPath)];
NSURL *itemURL = (nsPath ? [NSURL fileURLWithPath:nsPath] : nil);
NSURL *URL = [fileManager URLForDirectory:(NSSearchPathDirectory)directory
inDomain:domainMask
appropriateForURL:itemURL
create:create error:NULL];
return URL.path.fileSystemRepresentation;
}

View File

@ -182,4 +182,4 @@ namespace Ship
}
}
#endif
#endif

View File

@ -1,138 +1,54 @@
#include "SDLController.h"
#include "GameSettings.h"
#include "GlobalCtx2.h"
#include "spdlog/spdlog.h"
#include "stox.h"
#include "Window.h"
#include "Cvar.h"
#include <Utils/StringHelper.h>
extern "C" uint8_t __osMaxControllers;
namespace Ship {
SDLController::SDLController(int32_t dwControllerNumber) : Controller(dwControllerNumber), Cont(nullptr), guid(INVALID_SDL_CONTROLLER_GUID) {
}
SDLController::~SDLController() {
Close();
}
bool SDLController::IsGuidInUse(const std::string& guid) {
// Check if the GUID is loaded in any other controller;
for (size_t i = 0; i < __osMaxControllers; i++) {
for (size_t j = 0; j < Window::Controllers[i].size(); j++) {
SDLController* OtherCont = dynamic_cast<SDLController*>(Window::Controllers[i][j].get());
if (OtherCont != nullptr && OtherCont->GetGuid().compare(guid) == 0) {
return true;
}
}
}
return false;
}
bool SDLController::Open() {
std::string ConfSection = GetConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (SDL_IsGameController(i)) {
// Get the GUID from SDL
char GuidBuf[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i), GuidBuf, sizeof(GuidBuf));
auto NewGuid = std::string(GuidBuf);
const auto NewCont = SDL_GameControllerOpen(physicalSlot);
// Invalid GUID read. Go to next.
if (NewGuid.compare(INVALID_SDL_CONTROLLER_GUID) == 0) {
SPDLOG_ERROR("SDL Controller returned invalid guid");
continue;
}
// The GUID is in use, we want to use a different physical controller. Go to next.
if (IsGuidInUse(NewGuid)) {
continue;
}
// If the GUID is blank from the config, OR if the config GUID matches, load the controller.
if (Conf[ConfSection]["GUID"].compare("") == 0 || Conf[ConfSection]["GUID"].compare(INVALID_SDL_CONTROLLER_GUID) == 0 || Conf[ConfSection]["GUID"].compare(NewGuid) == 0) {
auto NewCont = SDL_GameControllerOpen(i);
// We failed to load the controller. Go to next.
if (NewCont == nullptr) {
SPDLOG_ERROR("SDL Controller failed to open: ({})", SDL_GetError());
continue;
}
if (SDL_GameControllerHasSensor(NewCont, SDL_SENSOR_GYRO))
{
SDL_GameControllerSetSensorEnabled(NewCont, SDL_SENSOR_GYRO, SDL_TRUE);
}
guid = NewGuid;
Cont = NewCont;
std::string BindingConfSection = GetBindingConfSection();
std::string PadConfSection = *GetPadConfSection();
std::shared_ptr<ConfigFile> config = GlobalCtx2::GetInstance()->GetConfig();
if (!config->has(BindingConfSection)) {
CreateDefaultBinding();
}
if (!config->has(PadConfSection)) {
CreateDefaultPadConf();
}
LoadBinding();
LoadAxisThresholds();
// Update per-controller settings in ImGui menu after opening controller.
Game::LoadPadSettings();
break;
}
}
// We failed to load the controller. Go to next.
if (NewCont == nullptr) {
SPDLOG_ERROR("SDL Controller failed to open: ({})", SDL_GetError());
return false;
}
return Cont != nullptr;
if (SDL_GameControllerHasSensor(NewCont, SDL_SENSOR_GYRO)) {
SDL_GameControllerSetSensorEnabled(NewCont, SDL_SENSOR_GYRO, SDL_TRUE);
supportsGyro = true;
}
char GuidBuf[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(physicalSlot), GuidBuf, sizeof(GuidBuf));
GUID = std::string(GuidBuf);
Cont = NewCont;
wCamX = 0;
wCamY = 0;
return true;
}
bool SDLController::Close() {
if (CanRumble()) {
SDL_GameControllerRumble(Cont, 0, 0, 0);
}
if (Cont != nullptr) {
if (Cont != nullptr && SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
if (CanRumble()) {
SDL_GameControllerRumble(Cont, 0, 0, 0);
}
SDL_GameControllerClose(Cont);
}
Cont = nullptr;
guid = "";
ButtonMapping.clear();
ThresholdMapping.clear();
dwPressedButtons = 0;
wStickX = 0;
wStickY = 0;
return true;
}
void SDLController::LoadAxisThresholds() {
std::string ConfSection = GetBindingConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
ThresholdMapping[SDL_CONTROLLER_AXIS_LEFTX] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTX) + "_threshold"]);
ThresholdMapping[SDL_CONTROLLER_AXIS_LEFTY] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTY) + "_threshold"]);
ThresholdMapping[SDL_CONTROLLER_AXIS_RIGHTX] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTX) + "_threshold"]);
ThresholdMapping[SDL_CONTROLLER_AXIS_RIGHTY] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTY) + "_threshold"]);
ThresholdMapping[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERLEFT) + "_threshold"]);
ThresholdMapping[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + "_threshold"]);
}
void SDLController::NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold) {
void SDLController::NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick, float sensitivity) {
//scale {-32768 ... +32767} to {-84 ... +84}
auto ax = wAxisValueX * 85.0 / 32767.0;
auto ay = wAxisValueY * 85.0 / 32767.0;
@ -163,14 +79,59 @@ namespace Ship {
ay *= scale;
}
wStickX = +ax;
wStickY = -ay;
if (!isRightStick) {
wStickX = +ax;
wStickY = -ay;
} else {
wCamX = +ax * sensitivity;
wCamY = -ay * sensitivity;
}
}
void SDLController::ReadFromSource() {
std::string ConfSection = GetBindingConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
int32_t SDLController::ReadRawPress() {
SDL_GameControllerUpdate();
for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
if (SDL_GameControllerGetButton(Cont, static_cast<SDL_GameControllerButton>(i))) {
return i;
}
}
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
const auto Axis = static_cast<SDL_GameControllerAxis>(i);
const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis) / 32767;
if(AxisValue < 0) {
return -(Axis + AXIS_SCANCODE_BIT);
}
if (AxisValue > 0) {
return (Axis + AXIS_SCANCODE_BIT);
}
}
return -1;
}
ControllerThresholds SDLAxisToThreshold( uint32_t axis ){
switch(axis){
case SDL_CONTROLLER_AXIS_LEFTX:
case SDL_CONTROLLER_AXIS_LEFTY:
return LEFT_STICK;
case SDL_CONTROLLER_AXIS_RIGHTX:
case SDL_CONTROLLER_AXIS_RIGHTY:
return RIGHT_STICK;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
return LEFT_TRIGGER;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
return RIGHT_TRIGGER;
default: return DRIFT_X;
}
}
void SDLController::ReadFromSource(int32_t slot) {
DeviceProfile& profile = profiles[slot];
SDL_GameControllerUpdate();
@ -187,17 +148,14 @@ namespace Ship {
}
}
if (SDL_GameControllerHasSensor(Cont, SDL_SENSOR_GYRO))
{
size_t contNumber = GetControllerNumber();
if (supportsGyro && profile.UseGyro) {
float gyroData[3];
SDL_GameControllerGetSensorData(Cont, SDL_SENSOR_GYRO, gyroData, 3);
const char* contName = SDL_GameControllerName(Cont);
float gyro_drift_x = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", contNumber).c_str(), 0.0f);
float gyro_drift_y = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", contNumber).c_str(), 0.0f);
const float gyro_sensitivity = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroSensitivity", contNumber).c_str(), 1.0f);
float gyro_drift_x = profile.GyroThresholds[DRIFT_X] / 100.0f;
float gyro_drift_y = profile.GyroThresholds[DRIFT_Y] / 100.0f;
const float gyro_sensitivity = profile.GyroThresholds[SENSITIVITY] / 100.0f;
if (gyro_drift_x == 0) {
gyro_drift_x = gyroData[0];
@ -207,8 +165,8 @@ namespace Ship {
gyro_drift_y = gyroData[1];
}
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", contNumber).c_str(), gyro_drift_x);
CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", contNumber).c_str(), gyro_drift_y);
profile.GyroThresholds[DRIFT_X] = (int) gyro_drift_x * 100;
profile.GyroThresholds[DRIFT_Y] = (int) gyro_drift_y * 100;
wGyroX = gyroData[0] - gyro_drift_x;
wGyroY = gyroData[1] - gyro_drift_y;
@ -218,28 +176,32 @@ namespace Ship {
}
for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
if (ButtonMapping.contains(i)) {
if (SDL_GameControllerGetButton(Cont, (SDL_GameControllerButton)i)) {
dwPressedButtons |= ButtonMapping[i];
if (profile.Mappings.contains(i)) {
if (SDL_GameControllerGetButton(Cont, static_cast<SDL_GameControllerButton>(i))) {
dwPressedButtons[slot] |= profile.Mappings[i];
}
else {
dwPressedButtons &= ~ButtonMapping[i];
dwPressedButtons[slot] &= ~profile.Mappings[i];
}
}
}
SDL_GameControllerAxis StickAxisX = SDL_CONTROLLER_AXIS_INVALID;
SDL_GameControllerAxis StickAxisY = SDL_CONTROLLER_AXIS_INVALID;
int32_t StickDeadzone = 0;
SDL_GameControllerAxis LStickAxisX = SDL_CONTROLLER_AXIS_INVALID;
SDL_GameControllerAxis LStickAxisY = SDL_CONTROLLER_AXIS_INVALID;
int32_t LStickDeadzone = 0;
SDL_GameControllerAxis RStickAxisX = SDL_CONTROLLER_AXIS_INVALID;
SDL_GameControllerAxis RStickAxisY = SDL_CONTROLLER_AXIS_INVALID;
int32_t RStickDeadzone = 0;
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
auto Axis = (SDL_GameControllerAxis)i;
auto PosScancode = i + AXIS_SCANCODE_BIT;
auto NegScancode = -PosScancode;
auto AxisThreshold = ThresholdMapping[i];
auto PosButton = ButtonMapping[PosScancode];
auto NegButton = ButtonMapping[NegScancode];
auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis);
const auto Axis = static_cast<SDL_GameControllerAxis>(i);
const auto PosScancode = i + AXIS_SCANCODE_BIT;
const auto NegScancode = -PosScancode;
const auto AxisThreshold = profile.Thresholds[SDLAxisToThreshold(i)];
const auto PosButton = profile.Mappings[PosScancode];
const auto NegButton = profile.Mappings[NegScancode];
const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis);
#ifdef TARGET_WEB
// Firefox has a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1606562
@ -252,94 +214,176 @@ namespace Ship {
}
#endif
// If the axis is NOT mapped to the control stick.
if (!(
PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT ||
PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN ||
NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT ||
NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN)) {
if (AxisValue > AxisThreshold) {
dwPressedButtons |= PosButton;
dwPressedButtons &= ~NegButton;
if (AxisValue > 0x1E00) {
dwPressedButtons[slot] |= PosButton;
dwPressedButtons[slot] &= ~NegButton;
}
else if (AxisValue < -AxisThreshold) {
dwPressedButtons &= ~PosButton;
dwPressedButtons |= NegButton;
else if (AxisValue < -0x1E00) {
dwPressedButtons[slot] &= ~PosButton;
dwPressedButtons[slot] |= NegButton;
}
else {
dwPressedButtons &= ~PosButton;
dwPressedButtons &= ~NegButton;
dwPressedButtons[slot] &= ~PosButton;
dwPressedButtons[slot] &= ~NegButton;
}
}
else {
if (PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT) {
if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisX != Axis) {
SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", StickAxisX, Axis);
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) {
SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", LStickAxisX, Axis);
}
if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", StickDeadzone, AxisThreshold);
if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", LStickDeadzone, AxisThreshold);
}
StickDeadzone = AxisThreshold;
StickAxisX = Axis;
LStickDeadzone = AxisThreshold;
LStickAxisX = Axis;
}
if (PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN) {
if (StickAxisY != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != Axis) {
SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", StickAxisY, Axis);
if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) {
SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", LStickAxisY, Axis);
}
if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold);
if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold);
}
StickDeadzone = AxisThreshold;
StickAxisY = Axis;
LStickDeadzone = AxisThreshold;
LStickAxisY = Axis;
}
if (NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT) {
if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisX != Axis) {
SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", StickAxisX, Axis);
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) {
SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", LStickAxisX, Axis);
}
if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold);
if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold);
}
StickDeadzone = AxisThreshold;
StickAxisX = Axis;
LStickDeadzone = AxisThreshold;
LStickAxisX = Axis;
}
if (NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN) {
if (StickAxisY != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != Axis) {
SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", StickAxisY, Axis);
if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) {
SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", LStickAxisY, Axis);
}
if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold);
if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold);
}
StickDeadzone = AxisThreshold;
StickAxisY = Axis;
LStickDeadzone = AxisThreshold;
LStickAxisY = Axis;
}
}
if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != SDL_CONTROLLER_AXIS_INVALID) {
auto AxisValueX = SDL_GameControllerGetAxis(Cont, StickAxisX);
auto AxisValueY = SDL_GameControllerGetAxis(Cont, StickAxisY);
NormalizeStickAxis(AxisValueX, AxisValueY, StickDeadzone);
if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != SDL_CONTROLLER_AXIS_INVALID) {
const auto AxisValueX = SDL_GameControllerGetAxis(Cont, LStickAxisX);
const auto AxisValueY = SDL_GameControllerGetAxis(Cont, LStickAxisY);
NormalizeStickAxis(AxisValueX, AxisValueY, LStickDeadzone, false, profile.Thresholds[SENSITIVITY]);
}
// Right Stick
// If the axis is NOT mapped to the control stick.
if (!(
PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT ||
PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN ||
NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT ||
NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN)) {
if (AxisValue > 0x1E00) {
dwPressedButtons[slot] |= PosButton;
dwPressedButtons[slot] &= ~NegButton;
}
else if (AxisValue < -0x1E00) {
dwPressedButtons[slot] &= ~PosButton;
dwPressedButtons[slot] |= NegButton;
}
else {
dwPressedButtons[slot] &= ~PosButton;
dwPressedButtons[slot] &= ~NegButton;
}
} else {
if (PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT) {
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) {
SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", RStickAxisX, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", RStickDeadzone, AxisThreshold);
}
RStickDeadzone = AxisThreshold;
RStickAxisX = Axis;
}
if (PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN) {
if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) {
SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", RStickAxisY, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold);
}
RStickDeadzone = AxisThreshold;
RStickAxisY = Axis;
}
if (NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT) {
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) {
SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", RStickAxisX, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold);
}
RStickDeadzone = AxisThreshold;
RStickAxisX = Axis;
}
if (NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN) {
if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) {
SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", RStickAxisY, Axis);
}
if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) {
SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold);
}
RStickDeadzone = AxisThreshold;
RStickAxisY = Axis;
}
}
if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != SDL_CONTROLLER_AXIS_INVALID) {
const auto AxisValueX = SDL_GameControllerGetAxis(Cont, RStickAxisX);
const auto AxisValueY = SDL_GameControllerGetAxis(Cont, RStickAxisY);
NormalizeStickAxis(AxisValueX, AxisValueY, RStickDeadzone, true, profile.Thresholds[SENSITIVITY]);
}
}
}
void SDLController::WriteToSource(ControllerCallback* controller)
void SDLController::WriteToSource(int32_t slot, ControllerCallback* controller)
{
if (CanRumble()) {
if (CanRumble() && profiles[slot].UseRumble) {
if (controller->rumble > 0) {
float rumble_strength = CVar_GetFloat(StringHelper::Sprintf("gCont%i_RumbleStrength", GetControllerNumber()).c_str(), 1.0f);
float rumble_strength = profiles[slot].RumbleStrength;
SDL_GameControllerRumble(Cont, 0xFFFF * rumble_strength, 0xFFFF * rumble_strength, 0);
} else {
}
else {
SDL_GameControllerRumble(Cont, 0, 0, 0);
}
}
@ -362,73 +406,71 @@ namespace Ship {
}
}
void SDLController::CreateDefaultBinding() {
std::string ConfSection = GetBindingConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
const char* AxisNames[] = {
"Left Stick X",
"Left Stick Y",
"Right Stick X",
"Right Stick Y",
"Left Trigger",
"Right Trigger",
"Start Button"
};
Conf[ConfSection][STR(BTN_CRIGHT)] = std::to_string((SDL_CONTROLLER_AXIS_RIGHTX + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_CLEFT)] = std::to_string(-(SDL_CONTROLLER_AXIS_RIGHTX + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_CDOWN)] = std::to_string((SDL_CONTROLLER_AXIS_RIGHTY + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_CUP)] = std::to_string(-(SDL_CONTROLLER_AXIS_RIGHTY + AXIS_SCANCODE_BIT));
//Conf[ConfSection][STR(BTN_CRIGHT + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_X);
//Conf[ConfSection][STR(BTN_CLEFT + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_Y);
//Conf[ConfSection][STR(BTN_CDOWN + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
//Conf[ConfSection][STR(BTN_CUP + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSTICK);
Conf[ConfSection][STR(BTN_R)] = std::to_string((SDL_CONTROLLER_AXIS_TRIGGERRIGHT + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_L)] = std::to_string(SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
Conf[ConfSection][STR(BTN_DRIGHT)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
Conf[ConfSection][STR(BTN_DLEFT)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
Conf[ConfSection][STR(BTN_DDOWN)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
Conf[ConfSection][STR(BTN_DUP)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_UP);
Conf[ConfSection][STR(BTN_START)] = std::to_string(SDL_CONTROLLER_BUTTON_START);
Conf[ConfSection][STR(BTN_Z)] = std::to_string((SDL_CONTROLLER_AXIS_TRIGGERLEFT + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_B)] = std::to_string(SDL_CONTROLLER_BUTTON_B);
Conf[ConfSection][STR(BTN_A)] = std::to_string(SDL_CONTROLLER_BUTTON_A);
Conf[ConfSection][STR(BTN_STICKRIGHT)] = std::to_string((SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_STICKLEFT)] = std::to_string(-(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_STICKDOWN)] = std::to_string((SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT));
Conf[ConfSection][STR(BTN_STICKUP)] = std::to_string(-(SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT));
char buffer[50];
const char* SDLController::GetButtonName(int slot, int n64Button) {
std::map<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair<int32_t, int32_t>& pair) {
return pair.second == n64Button;
});
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTX) + "_threshold"] = std::to_string(16.0);
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTY) + "_threshold"] = std::to_string(16.0);
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTX) + "_threshold"] = std::to_string(0x4000);
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTY) + "_threshold"] = std::to_string(0x4000);
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERLEFT) + "_threshold"] = std::to_string(0x1E00);
Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + "_threshold"] = std::to_string(0x1E00);
if (find == Mappings.end()) return "Unknown";
Conf.Save();
}
int btn = abs(find->first);
void SDLController::CreateDefaultPadConf() {
std::string ConfSection = *GetPadConfSection();
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
if(btn >= AXIS_SCANCODE_BIT) {
btn -= AXIS_SCANCODE_BIT;
Conf.Save();
}
void SDLController::SetButtonMapping(const std::string& szButtonName, int32_t dwScancode) {
if (guid.compare(INVALID_SDL_CONTROLLER_GUID)) {
return;
snprintf(buffer, sizeof(buffer), "%s%s", AxisNames[btn], find->first > 0 ? "+" : "-");
return buffer;
}
Controller::SetButtonMapping(szButtonName, dwScancode);
snprintf(buffer, sizeof(buffer), "Button %d", btn);
return buffer;
}
std::string SDLController::GetControllerType() {
return "SDL";
}
std::string SDLController::GetConfSection() {
return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1);
const char* SDLController::GetControllerName() {
return SDL_GameControllerNameForIndex(physicalSlot);
}
std::string SDLController::GetBindingConfSection() {
return GetControllerType() + " CONTROLLER BINDING " + guid;
}
void SDLController::CreateDefaultBinding(int32_t slot) {
DeviceProfile& profile = profiles[slot];
profile.Mappings.clear();
std::optional<std::string> SDLController::GetPadConfSection() {
return GetControllerType() + " CONTROLLER PAD " + guid;
profile.UseRumble = true;
profile.RumbleStrength = 1.0f;
profile.UseGyro = false;
profile.Mappings[ SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT] = BTN_CRIGHT;
profile.Mappings[-(SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT)] = BTN_CLEFT;
profile.Mappings[ SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT] = BTN_CDOWN;
profile.Mappings[-(SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT)] = BTN_CUP;
profile.Mappings[SDL_CONTROLLER_AXIS_TRIGGERRIGHT + AXIS_SCANCODE_BIT] = BTN_R;
profile.Mappings[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = BTN_L;
profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = BTN_DRIGHT;
profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = BTN_DLEFT;
profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = BTN_DDOWN;
profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_UP] = BTN_DUP;
profile.Mappings[SDL_CONTROLLER_BUTTON_START] = BTN_START;
profile.Mappings[SDL_CONTROLLER_AXIS_TRIGGERLEFT + AXIS_SCANCODE_BIT] = BTN_Z;
profile.Mappings[SDL_CONTROLLER_BUTTON_B] = BTN_B;
profile.Mappings[SDL_CONTROLLER_BUTTON_A] = BTN_A;
profile.Mappings[(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)] = BTN_STICKRIGHT;
profile.Mappings[-(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)] = BTN_STICKLEFT;
profile.Mappings[SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT] = BTN_STICKDOWN;
profile.Mappings[-(SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT)] = BTN_STICKUP;
profile.Thresholds[LEFT_STICK] = 16.0;
profile.Thresholds[RIGHT_STICK] = 16.0;
profile.Thresholds[LEFT_TRIGGER] = 0x1E00;
profile.Thresholds[RIGHT_TRIGGER] = 0x1E00;
profile.Thresholds[SENSITIVITY] = 16.0;
}
}

View File

@ -6,46 +6,35 @@
#include <SDL2/SDL.h>
#endif
#define INVALID_SDL_CONTROLLER_GUID (std::string("00000000000000000000000000000000"))
namespace Ship {
class SDLController : public Controller {
public:
SDLController(int32_t dwControllerNumber);
~SDLController();
void ReadFromSource();
void WriteToSource(ControllerCallback* controller);
bool Connected() const { return Cont != nullptr; }
bool CanRumble() const {
SDLController(int slot) : Controller(), Cont(nullptr), physicalSlot(slot) { }
void ReadFromSource(int32_t slot) override;
const char* GetControllerName() override;
const char* GetButtonName(int slot, int n64Button) override;
void WriteToSource(int32_t slot, ControllerCallback* controller) override;
bool Connected() const override { return Cont != nullptr; }
bool CanGyro() const override { return supportsGyro; }
bool CanRumble() const override {
#if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2,0,18)
return SDL_GameControllerHasRumble(Cont);
#endif
return true;
}
std::string GetGuid() { return guid; };
bool HasPadConf() const { return true; }
std::optional<std::string> GetPadConfSection();
bool Open();
void ClearRawPress() override {}
int32_t ReadRawPress() override;
protected:
std::string GetControllerType();
void SetButtonMapping(const std::string& szButtonName, int32_t dwScancode);
std::string GetConfSection();
std::string GetBindingConfSection();
void CreateDefaultBinding();
void CreateDefaultPadConf();
static bool IsGuidInUse(const std::string& guid);
void CreateDefaultBinding(int32_t slot) override;
private:
SDL_GameController* Cont;
std::string guid;
std::map<int32_t, int16_t> ThresholdMapping;
void LoadAxisThresholds();
void NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold);
bool Open();
int physicalSlot;
bool supportsGyro;
void NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick, float sensitivity);
bool Close();
};
}

View File

@ -101,6 +101,10 @@
#define BTN_STICKRIGHT 0x20000
#define BTN_STICKDOWN 0x40000
#define BTN_STICKUP 0x80000
#define BTN_VSTICKUP 0x100000
#define BTN_VSTICKDOWN 0x200000
#define BTN_VSTICKLEFT 0x400000
#define BTN_VSTICKRIGHT 0x800000
typedef struct {
/* 0x00 */ int32_t ram[15];
@ -120,7 +124,9 @@ typedef struct {
/* 0x04 */ uint8_t err_no;
/* 0x05 */ float gyro_x;
/* 0x09 */ float gyro_y;
} OSContPad; // size = 0x0D
/* 0x1C */ float cam_x;
/* 0x20 */ float cam_y;
} OSContPad; // size = 0x24
typedef struct {
/* 0x00 */ uint8_t rumble;

View File

@ -38,8 +38,7 @@ extern "C" {
uint8_t __enableGameInput = 1;
int32_t osContInit(OSMesgQueue* mq, uint8_t* controllerBits, OSContStatus* status) {
std::shared_ptr<Ship::ConfigFile> pConf = Ship::GlobalCtx2::GetInstance()->GetConfig();
Ship::ConfigFile& Conf = *pConf.get();
*controllerBits = 0;
if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) {
SPDLOG_ERROR("Failed to initialize SDL game controllers ({})", SDL_GetError());
@ -54,43 +53,7 @@ extern "C" {
SPDLOG_ERROR("Failed add SDL game controller mappings from \"{}\" ({})", controllerDb, SDL_GetError());
}
// TODO: This for loop is debug. Burn it with fire.
for (int i = 0; i < SDL_NumJoysticks(); i++) {
if (SDL_IsGameController(i)) {
// Get the GUID from SDL
char buf[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i), buf, sizeof(buf));
auto guid = std::string(buf);
auto name = std::string(SDL_GameControllerNameForIndex(i));
SPDLOG_INFO("Found Controller \"{}\" with ID \"{}\"", name, guid);
}
}
for (int32_t i = 0; i < __osMaxControllers; i++) {
std::string ControllerType = Conf["CONTROLLERS"]["CONTROLLER " + std::to_string(i+1)];
mINI::INIStringUtil::toLower(ControllerType);
if (ControllerType == "auto") {
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::KeyboardController>(i));
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::SDLController>(i));
} else if (ControllerType == "keyboard") {
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::KeyboardController>(i));
} else if (ControllerType == "usb") {
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::SDLController>(i));
} else if (ControllerType == "unplugged") {
// Do nothing for unplugged controllers
} else {
SPDLOG_ERROR("Invalid Controller Type: {}", ControllerType);
}
}
*controllerBits = 0;
for (size_t i = 0; i < __osMaxControllers; i++) {
if (Ship::Window::Controllers[i].size() > 0) {
*controllerBits |= 1 << i;
}
}
Ship::Window::ControllerApi->Init(controllerBits);
return 0;
}
@ -103,17 +66,14 @@ extern "C" {
pad->button = 0;
pad->stick_x = 0;
pad->stick_y = 0;
pad->cam_x = 0;
pad->cam_y = 0;
pad->err_no = 0;
pad->gyro_x = 0;
pad->gyro_y = 0;
if (__enableGameInput)
{
for (size_t i = 0; i < __osMaxControllers; i++) {
for (size_t j = 0; j < Ship::Window::Controllers[i].size(); j++) {
Ship::Window::Controllers[i][j]->Read(&pad[i]);
}
}
if (__enableGameInput) {
Ship::Window::ControllerApi->WriteToPad(pad);
}
ModInternal::ExecuteHooks<ModInternal::ControllerRead>(pad);
@ -129,15 +89,10 @@ extern "C" {
if (hashStr != nullptr) {
auto res = std::static_pointer_cast<Ship::Array>(Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str()));
return (Vtx*)res->vertices.data();
}
//if (res != nullptr)
return (Vtx*)res->vertices.data();
//else
//return (Vtx*)Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadFile(hashStr)->buffer.get();
}
else {
return nullptr;
}
return nullptr;
}
int32_t* ResourceMgr_LoadMtxByCRC(uint64_t crc) {
@ -146,9 +101,9 @@ extern "C" {
if (hashStr != nullptr) {
auto res = std::static_pointer_cast<Ship::Matrix>(Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str()));
return (int32_t*)res->mtx.data();
} else {
return nullptr;
}
return nullptr;
}
Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc) {
@ -233,7 +188,7 @@ extern GfxWindowManagerAPI gfx_sdl;
void SetWindowManager(GfxWindowManagerAPI** WmApi, GfxRenderingAPI** RenderingApi, const std::string& gfx_backend);
namespace Ship {
std::map<size_t, std::vector<std::shared_ptr<Controller>>> Window::Controllers;
int32_t Window::lastScancode;
Window::Window(std::shared_ptr<GlobalCtx2> Context) : Context(Context), APlayer(nullptr) {
@ -248,26 +203,49 @@ namespace Ship {
SPDLOG_INFO("destruct window");
}
void Window::CreateDefaults() {
const std::shared_ptr<Mercury> pConf = GlobalCtx2::GetInstance()->GetConfig();
if (pConf->isNewInstance) {
pConf->setInt("Window.Width", 640);
pConf->setInt("Window.Height", 480);
pConf->setBool("Window.Options", false);
pConf->setString("Window.GfxBackend", "");
pConf->setBool("Window.Fullscreen.Enabled", false);
pConf->setInt("Window.Fullscreen.Width", 640);
pConf->setInt("Window.Fullscreen.Height", 480);
pConf->setString("Game.SaveName", "");
pConf->setString("Game.Main Archive", "");
pConf->setString("Game.Patches Archive", "");
pConf->setInt("Shortcuts.Fullscreen", 0x044);
pConf->setInt("Shortcuts.Console", 0x029);
pConf->save();
}
}
void Window::Init() {
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> pConf = GlobalCtx2::GetInstance()->GetConfig();
CreateDefaults();
SetAudioPlayer();
bIsFullscreen = Ship::stob(Conf["WINDOW"]["FULLSCREEN"]);
if (bIsFullscreen) {
dwWidth = Ship::stoi(Conf["WINDOW"]["FULLSCREEN WIDTH"], 1920);
dwHeight = Ship::stoi(Conf["WINDOW"]["FULLSCREEN HEIGHT"], 1080);
} else {
dwWidth = Ship::stoi(Conf["WINDOW"]["WINDOW WIDTH"], 640);
dwHeight = Ship::stoi(Conf["WINDOW"]["WINDOW HEIGHT"], 480);
}
dwMenubar = Ship::stoi(Conf["WINDOW"]["menubar"], 0);
const std::string& gfx_backend = Conf["WINDOW"]["GFX BACKEND"];
bIsFullscreen = pConf->getBool("Window.Fullscreen.Enabled", false);
dwWidth = pConf->getInt("Window.Fullscreen.Width", bIsFullscreen ? 1920 : 640);
dwHeight = pConf->getInt("Window.Fullscreen.Height", bIsFullscreen ? 1080 : 480);
dwMenubar = pConf->getBool("Window.Options", false);
const std::string& gfx_backend = pConf->getString("Window.GfxBackend");
SetWindowManager(&WmApi, &RenderingApi, gfx_backend);
gfx_init(WmApi, RenderingApi, GetContext()->GetName().c_str(), bIsFullscreen, dwWidth, dwHeight);
WmApi->set_fullscreen_changed_callback(Window::OnFullscreenChanged);
WmApi->set_keyboard_callbacks(Window::KeyDown, Window::KeyUp, Window::AllKeysUp);
WmApi->set_fullscreen_changed_callback(OnFullscreenChanged);
WmApi->set_keyboard_callbacks(KeyDown, KeyUp, AllKeysUp);
ModInternal::RegisterHook<ModInternal::ExitGame>([]() {
ControllerApi->SaveControllerSettings();
});
}
void Window::StartFrame() {
@ -318,30 +296,26 @@ namespace Ship {
void Window::MainLoop(void (*MainFunction)(void)) {
WmApi->main_loop(MainFunction);
}
bool Window::KeyUp(int32_t dwScancode) {
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> pConf = GlobalCtx2::GetInstance()->GetConfig();
if (dwScancode == Ship::stoi(Conf["KEYBOARD SHORTCUTS"]["KEY_FULLSCREEN"])) {
if (dwScancode == pConf->getInt("Shortcuts.Fullscreen", 0x044)) {
GlobalCtx2::GetInstance()->GetWindow()->ToggleFullscreen();
}
// OTRTODO: Rig with Kirito's console?
//if (dwScancode == Ship::stoi(Conf["KEYBOARD SHORTCUTS"]["KEY_CONSOLE"])) {
// ToggleConsole();
//}
lastScancode = -1;
bool bIsProcessed = false;
for (size_t i = 0; i < __osMaxControllers; i++) {
for (size_t j = 0; j < Controllers[i].size(); j++) {
KeyboardController* pad = dynamic_cast<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
if (pad->ReleaseButton(dwScancode)) {
bIsProcessed = true;
}
}
const auto pad = dynamic_cast<KeyboardController*>(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get());
if (pad != nullptr) {
if (pad->ReleaseButton(dwScancode)) {
bIsProcessed = true;
}
}
@ -350,14 +324,11 @@ namespace Ship {
bool Window::KeyDown(int32_t dwScancode) {
bool bIsProcessed = false;
for (size_t i = 0; i < __osMaxControllers; i++) {
for (size_t j = 0; j < Controllers[i].size(); j++) {
KeyboardController* pad = dynamic_cast<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
if (pad->PressButton(dwScancode)) {
bIsProcessed = true;
}
}
const auto pad = dynamic_cast<KeyboardController*>(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get());
if (pad != nullptr) {
if (pad->PressButton(dwScancode)) {
bIsProcessed = true;
}
}
@ -368,21 +339,17 @@ namespace Ship {
void Window::AllKeysUp(void) {
for (size_t i = 0; i < __osMaxControllers; i++) {
for (size_t j = 0; j < Controllers[i].size(); j++) {
KeyboardController* pad = dynamic_cast<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
pad->ReleaseAllButtons();
}
}
const auto pad = dynamic_cast<KeyboardController*>(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get());
if (pad != nullptr) {
pad->ReleaseAllButtons();
}
}
void Window::OnFullscreenChanged(bool bIsFullscreen) {
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> pConf = GlobalCtx2::GetInstance()->GetConfig();
GlobalCtx2::GetInstance()->GetWindow()->bIsFullscreen = bIsFullscreen;
Conf["WINDOW"]["FULLSCREEN"] = std::to_string(bIsFullscreen);
pConf->setBool("Window.Fullscreen.Enabled", bIsFullscreen);
GlobalCtx2::GetInstance()->GetWindow()->ShowCursor(!bIsFullscreen);
}

View File

@ -5,17 +5,22 @@
#include "UltraController.h"
#include "Controller.h"
#include "GlobalCtx2.h"
#include "ControlDeck.h"
#include <string>
#include "Lib/Fast3D/gfx_window_manager_api.h"
namespace Ship {
class AudioPlayer;
class Window {
public:
static std::map<size_t, std::vector<std::shared_ptr<Controller>>> Controllers;
static int32_t lastScancode;
inline static ControlDeck* ControllerApi = new ControlDeck;
Window(std::shared_ptr<GlobalCtx2> Context);
~Window();
void CreateDefaults();
void MainLoop(void (*MainFunction)(void));
void Init();
void StartFrame();
@ -31,9 +36,11 @@ namespace Ship {
bool IsFullscreen() { return bIsFullscreen; }
uint32_t GetCurrentWidth();
uint32_t GetCurrentHeight();
ControlDeck* GetControlDeck() { return ControllerApi; };
uint32_t dwMenubar;
std::shared_ptr<GlobalCtx2> GetContext() { return Context.lock(); }
std::shared_ptr<AudioPlayer> GetAudioPlayer() { return APlayer; }
const char* GetKeyName(int scancode) { return WmApi->get_key_name(scancode); }
protected:
private:
@ -46,11 +53,10 @@ namespace Ship {
std::weak_ptr<GlobalCtx2> Context;
std::shared_ptr<AudioPlayer> APlayer;
GfxWindowManagerAPI* WmApi;
GfxRenderingAPI* RenderingApi;
GfxWindowManagerAPI* WmApi;
bool bIsFullscreen;
uint32_t dwWidth;
uint32_t dwHeight;
};
}

View File

@ -38,38 +38,38 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Testing|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Testing|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@ -256,13 +256,16 @@
<ItemGroup>
<ClCompile Include="Audio.cpp" />
<ClCompile Include="Blob.cpp" />
<ClCompile Include="ControlDeck.cpp" />
<ClCompile Include="Cvar.cpp" />
<ClCompile Include="Environment.cpp" />
<ClCompile Include="Factories\AudioFactory.cpp" />
<ClCompile Include="InputEditor.cpp" />
<ClCompile Include="GameOverlay.cpp" />
<ClCompile Include="GameSettings.cpp" />
<ClCompile Include="Lib\ImGui\backends\imgui_impl_dx11.cpp" />
<ClCompile Include="Lib\ImGui\backends\imgui_impl_win32.cpp" />
<ClCompile Include="Lib\Mercury\Mercury.cpp" />
<ClCompile Include="luslog.cpp" />
<ClCompile Include="mixer.c" />
<ClCompile Include="ModManager.cpp" />
@ -279,7 +282,6 @@
<ClCompile Include="Factories\TextureFactory.cpp" />
<ClCompile Include="Factories\VtxFactory.cpp" />
<ClCompile Include="Array.cpp" />
<ClCompile Include="ConfigFile.cpp" />
<ClCompile Include="Controller.cpp" />
<ClCompile Include="Hooks.cpp" />
<ClCompile Include="ImGuiImpl.cpp" />
@ -342,13 +344,18 @@
<ClCompile Include="SDLController.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Lib\Mercury\Mercury.h" />
<ClInclude Include="Lib\nlohmann\json.hpp" />
<ClInclude Include="abi.h" />
<ClInclude Include="Audio.h" />
<ClInclude Include="AudioPlayer.h" />
<ClInclude Include="Blob.h" />
<ClInclude Include="ControlDeck.h" />
<ClInclude Include="Cvar.h" />
<ClInclude Include="DisconnectedController.h" />
<ClInclude Include="Environment.h" />
<ClInclude Include="Factories\AudioFactory.h" />
<ClInclude Include="InputEditor.h" />
<ClInclude Include="GameOverlay.h" />
<ClInclude Include="GameSettings.h" />
<ClInclude Include="GameVersions.h" />
@ -404,7 +411,6 @@
<ClInclude Include="Vertex.h" />
<ClInclude Include="stox.h" />
<ClInclude Include="Lib\mINI\src\mini\ini.h" />
<ClInclude Include="ConfigFile.h" />
<ClInclude Include="Controller.h" />
<ClInclude Include="KeyboardController.h" />
<ClInclude Include="Factories\CollisionHeaderFactory.h" />

View File

@ -31,9 +31,6 @@
<Filter Include="Source Files\Globals">
<UniqueIdentifier>{c0f07350-c627-444e-9f66-23e19407ad9a}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Config">
<UniqueIdentifier>{9cf4833f-e90c-4a9d-8747-d47cde657beb}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Resources\Files">
<UniqueIdentifier>{2aa34c3b-6148-480f-a4fc-19c4e0f8c822}</UniqueIdentifier>
</Filter>
@ -94,6 +91,15 @@
<Filter Include="Source Files\Lib\dr_libs">
<UniqueIdentifier>{db6e02cc-fc4c-4138-8219-1d281ad93ec2}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Lib\nlohmann">
<UniqueIdentifier>{2be7c90f-ba21-455d-8a11-6f99452be15c}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Lib\Mercury">
<UniqueIdentifier>{7e415dd2-403b-4d4d-b4f2-3e311f91db19}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Controller\InputEditor">
<UniqueIdentifier>{010dc29b-d1f6-4793-a4e7-4156aa4fcdd6}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Factories\MaterialFactory.cpp">
@ -165,9 +171,6 @@
<ClCompile Include="MemoryPack.cpp">
<Filter>Source Files\Controller\Attachment</Filter>
</ClCompile>
<ClCompile Include="ConfigFile.cpp">
<Filter>Source Files\Config</Filter>
</ClCompile>
<ClCompile Include="CollisionHeader.cpp">
<Filter>Source Files\Resources\Files</Filter>
</ClCompile>
@ -354,6 +357,15 @@
<ClCompile Include="Factories\AudioFactory.cpp">
<Filter>Source Files\Resources\Factories</Filter>
</ClCompile>
<ClCompile Include="InputEditor.cpp">
<Filter>Source Files\Controller\InputEditor</Filter>
</ClCompile>
<ClCompile Include="ControlDeck.cpp">
<Filter>Source Files\Controller</Filter>
</ClCompile>
<ClCompile Include="Lib\Mercury\Mercury.cpp">
<Filter>Source Files\Lib\Mercury</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Lib\tinyxml2\tinyxml2.h">
@ -386,9 +398,6 @@
<ClInclude Include="MemoryPack.h">
<Filter>Source Files\Controller\Attachment</Filter>
</ClInclude>
<ClInclude Include="ConfigFile.h">
<Filter>Source Files\Config</Filter>
</ClInclude>
<ClInclude Include="ResourceMgr.h">
<Filter>Source Files\Resources</Filter>
</ClInclude>
@ -659,5 +668,20 @@
<ClInclude Include="Lib\dr_libs\wav.h">
<Filter>Source Files\Lib\dr_libs</Filter>
</ClInclude>
<ClInclude Include="InputEditor.h">
<Filter>Source Files\Controller\InputEditor</Filter>
</ClInclude>
<ClInclude Include="ControlDeck.h">
<Filter>Source Files\Controller</Filter>
</ClInclude>
<ClInclude Include="DisconnectedController.h">
<Filter>Source Files\Controller</Filter>
</ClInclude>
<ClInclude Include="Lib\nlohmann\json.hpp">
<Filter>Source Files\Lib\nlohmann</Filter>
</ClInclude>
<ClInclude Include="Lib\Mercury\Mercury.h">
<Filter>Source Files\Lib\Mercury</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -4,7 +4,9 @@
#include "mixer.h"
#ifndef __clang__
#pragma GCC optimize ("unroll-loops")
#endif
#define ROUND_UP_64(v) (((v) + 63) & ~63)
#define ROUND_UP_32(v) (((v) + 31) & ~31)
@ -449,10 +451,14 @@ void aFilterImpl(uint8_t flags, uint16_t count_or_buf, int16_t *state_or_filter)
int16_t *buf = BUF_S16(count_or_buf);
if (flags == A_INIT) {
#ifndef __clang__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmemset-elt-size"
#endif
memset(tmp, 0, 8 * sizeof(int16_t));
#ifndef __clang__
#pragma GCC diagnostic pop
#endif
memset(tmp2, 0, 8 * sizeof(int16_t));
} else {
memcpy(tmp, state_or_filter, 8 * sizeof(int16_t));

View File

@ -18,8 +18,11 @@ LTO ?= 0
WARN := \
-Wno-return-type \
-Wno-unused-command-line-argument \
-Wno-implicit-function-declaration \
-Wno-c++11-narrowing \
-funsigned-char \
-fno-stack-protector -fno-common -fno-zero-initialized-in-bss -fno-strict-aliasing -fno-inline-functions -fno-inline-small-functions -fno-toplevel-reorder -ffreestanding -fwrapv \
-fno-stack-protector -fno-common -fno-zero-initialized-in-bss -fno-strict-aliasing -fno-inline-functions -fno-inline-small-functions -ffreestanding -fwrapv \
CXXFLAGS := $(WARN) -std=c++20 -D_GNU_SOURCE -fpermissive -no-pie -nostdlib
CFLAGS := $(WARN) -std=c99 -D_GNU_SOURCE -no-pie -nostdlib
@ -99,7 +102,6 @@ LDLIBS := \
bz2 \
z \
pthread \
atomic \
ultraship \
)
@ -116,8 +118,9 @@ endif
ifeq ($(UNAME), Darwin) #APPLE
LDLIBS += \
$(addprefix -framework, \
$(addprefix -framework , \
OpenGL \
Foundation \
) \
$(shell sdl2-config --libs) $(shell pkg-config --libs glew)
endif
@ -208,7 +211,7 @@ build/%.o: %.c
$(TARGET): $(LIBULTRASHIP)
$(TARGET): $(O_FILES)
$(CXX) $^ -o $@ $(LDFLAGS) -fuse-ld=$(LD) $(LDDIRS) $(LDLIBS)
$(CXX) $^ -o $@ $(LDFLAGS) $(LDDIRS) $(LDLIBS)
-include $(D_FILES)
@ -222,9 +225,6 @@ appbundle: macosx/$(APPNAME).icns
cp macosx/PkgInfo $(APPBUNDLECONTENTS)/
cp macosx/$(APPNAME).icns $(APPBUNDLEICON)/
cp $(TARGET) $(APPBUNDLEEXE)/soh
cp macosx/launcher.sh $(APPBUNDLEEXE)/launcher.sh
clang -ObjC macosx/appsupport.m -arch arm64 -arch x86_64 -framework Foundation -o macosx/appsupport
cp macosx/appsupport $(APPBUNDLEEXE)/appsupport
otool -l $(TARGET) | grep -A 2 LC_RPATH | tail -n 1 | awk '{print $2}' | dylibbundler -od -b -x $(APPBUNDLEEXE)/soh -d $(APPBUNDLECONTENTS)/libs
macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png
@ -242,7 +242,3 @@ macosx/$(APPNAME).icns: macosx/$(APPNAME)Icon.png
cp macosx/$(APPNAME)Icon.png macosx/$(APPNAME).iconset/icon_512x512@2x.png
iconutil -c icns -o macosx/$(APPNAME).icns macosx/$(APPNAME).iconset
rm -r macosx/$(APPNAME).iconset
filledappbundle: appbundle
cp ./oot.otr $(APPBUNDLEEXE)/oot.otr

View File

@ -448,6 +448,7 @@ u32 Actor_TextboxIsClosing(Actor* actor, GlobalContext* globalCtx);
s8 func_8002F368(GlobalContext* globalCtx);
void Actor_GetScreenPos(GlobalContext* globalCtx, Actor* actor, s16* x, s16* y);
u32 Actor_HasParent(Actor* actor, GlobalContext* globalCtx);
s32 GiveItemWithoutActor(GlobalContext* globalCtx, s32 getItemId);
s32 func_8002F434(Actor* actor, GlobalContext* globalCtx, s32 getItemId, f32 xzRange, f32 yRange);
void func_8002F554(Actor* actor, GlobalContext* globalCtx, s32 getItemId);
void func_8002F580(Actor* actor, GlobalContext* globalCtx);
@ -558,6 +559,7 @@ void ActorOverlayTable_Cleanup(void);
u16 DynaSSNodeList_GetNextNodeIdx(DynaSSNodeList*);
void func_80038A28(CollisionPoly* poly, f32 tx, f32 ty, f32 tz, MtxF* dest);
f32 CollisionPoly_GetPointDistanceFromPlane(CollisionPoly* poly, Vec3f* point);
CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId);
void CollisionPoly_GetVerticesByBgId(CollisionPoly* poly, s32 bgId, CollisionContext* colCtx, Vec3f* dest);
s32 BgCheck_CheckStaticCeiling(StaticLookup* lookup, u16 xpFlags, CollisionContext* colCtx, f32* outY, Vec3f* pos,
f32 checkHeight, CollisionPoly** outPoly);
@ -2397,6 +2399,8 @@ char* SetQuote();
void Heaps_Alloc(void);
void Heaps_Free(void);
CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId);
#ifdef __cplusplus
#undef this
};

View File

@ -171,10 +171,6 @@ extern "C"
#define gTatumsPerBeat (D_8014A6C0[1])
extern const AudioContextInitSizes D_8014A6C4;
extern s16 gOcarinaSongItemMap[];
extern u8 gSoundFontTable[];
extern u8 gSequenceFontTable[];
extern u8 gSequenceTable[];
extern u8 gSampleBankTable[];
extern u8 D_80155F50[];
extern u8 D_80157580[];
extern u8 D_801579A0[];

View File

@ -1203,6 +1203,9 @@ typedef struct GlobalContext {
/* 0x00790 */ Camera* cameraPtrs[NUM_CAMS];
/* 0x007A0 */ s16 activeCamera;
/* 0x007A2 */ s16 nextCamera;
/* 0x007A2 */ bool manualCamera;
/* 0x007A2 */ f32 camX;
/* 0x007A2 */ f32 camY;
/* 0x007A4 */ SequenceContext sequenceCtx;
/* 0x007A8 */ LightContext lightCtx;
/* 0x007B8 */ FrameAdvanceContext frameAdvCtx;
@ -1293,6 +1296,7 @@ typedef struct {
/* 0x1C9EC */ Vtx* keyboardVtx;
/* 0x1C9F0 */ Vtx* nameEntryVtx;
/* 0x1C9F4 */ u8 n64ddFlag;
/* 0x1CA28 */ s16 n64ddFlags[3];
/* 0x1CA38 */ s16 buttonIndex;
/* 0x1CA3A */ s16 confirmButtonIndex; // 0: yes, 1: quit
/* 0x1CA3C */ s16 menuMode;

View File

@ -289,6 +289,7 @@ typedef struct EnItem00 {
/* 0x15A */ s16 unk_15A;
/* 0x15C */ f32 scale;
/* 0x160 */ ColliderCylinder collider;
s16 ogParams;
} EnItem00; // size = 0x1AC
// Only A_OBJ_SIGNPOST_OBLONG and A_OBJ_SIGNPOST_ARROW are used in room files.

View File

@ -20,7 +20,6 @@
#define CALC_RESAMPLE_FREQ(sampleRate) ((float)sampleRate / (s32)gAudioContext.audioBufferParameters.frequency)
extern bool gUseLegacySD;
extern char* fontMap[256];
typedef enum {

View File

@ -89,6 +89,7 @@ typedef struct {
/* 0x20 */ CamData* cameraDataList;
/* 0x24 */ u16 numWaterBoxes;
/* 0x28 */ WaterBox* waterBoxes;
size_t cameraDataListLen; // OTRTODO: Added to allow for bounds checking the cameraDataList.
} CollisionHeader; // original name: BGDataInfo
typedef struct {

View File

@ -211,9 +211,9 @@ typedef enum {
/* 0x78 */ ITEM_MAGIC_SMALL,
/* 0x79 */ ITEM_MAGIC_LARGE,
/* 0x7A */ ITEM_HEART_PIECE_2,
/* 0x7B */ ITEM_INVALID_1,
/* 0x7C */ ITEM_INVALID_2,
/* 0x7D */ ITEM_INVALID_3,
/* 0x7B */ ITEM_SINGLE_MAGIC,
/* 0x7C */ ITEM_DOUBLE_MAGIC,
/* 0x7D */ ITEM_DOUBLE_DEFENSE,
/* 0x7E */ ITEM_INVALID_4,
/* 0x7F */ ITEM_INVALID_5,
/* 0x80 */ ITEM_INVALID_6,
@ -244,6 +244,15 @@ typedef enum {
/* 0x99 */ ITEM_STICK_UPGRADE_30,
/* 0x9A */ ITEM_NUT_UPGRADE_30,
/* 0x9B */ ITEM_NUT_UPGRADE_40,
/* 0x9C */ ITEM_BOTTLE_WITH_RED_POTION,
/* 0x9D */ ITEM_BOTTLE_WITH_GREEN_POTION,
/* 0x9E */ ITEM_BOTTLE_WITH_BLUE_POTION,
/* 0x9F */ ITEM_BOTTLE_WITH_FAIRY,
/* 0xA0 */ ITEM_BOTTLE_WITH_FISH,
/* 0xA1 */ ITEM_BOTTLE_WITH_BLUE_FIRE,
/* 0xA2 */ ITEM_BOTTLE_WITH_BUGS,
/* 0xA3 */ ITEM_BOTTLE_WITH_POE,
/* 0xA4 */ ITEM_BOTTLE_WITH_BIG_POE,
/* 0xFC */ ITEM_LAST_USED = 0xFC,
/* 0xFE */ ITEM_NONE_FE = 0xFE,
/* 0xFF */ ITEM_NONE = 0xFF
@ -380,7 +389,47 @@ typedef enum {
/* 0x7B */ GI_BULLET_BAG_50,
/* 0x7C */ GI_ICE_TRAP, // freezes link when opened from a chest
/* 0x7D */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg)
/* 0x7E */ GI_MAX
/* 0x7E */ GI_MEDALLION_LIGHT,
/* 0x7F */ GI_MEDALLION_FOREST,
/* 0x80 */ GI_MEDALLION_FIRE,
/* 0x81 */ GI_MEDALLION_WATER,
/* 0x82 */ GI_MEDALLION_SHADOW,
/* 0x83 */ GI_MEDALLION_SPIRIT,
/* 0x81 */ GI_STONE_KOKIRI,
/* 0x82 */ GI_STONE_GORON,
/* 0x83 */ GI_STONE_ZORA,
/* 0x81 */ GI_ZELDAS_LULLABY,
/* 0x82 */ GI_SUNS_SONG,
/* 0x83 */ GI_EPONAS_SONG,
/* 0x81 */ GI_SONG_OF_STORMS,
/* 0x82 */ GI_SONG_OF_TIME,
/* 0x83 */ GI_SARIAS_SONG,
/* 0x81 */ GI_MINUET_OF_FOREST,
/* 0x82 */ GI_BOLERO_OF_FIRE,
/* 0x83 */ GI_SERENADE_OF_WATER,
/* 0x81 */ GI_NOCTURNE_OF_SHADOW,
/* 0x82 */ GI_REQUIEM_OF_SPIRIT,
/* 0x83 */ GI_PRELUDE_OF_LIGHT,
GI_SINGLE_MAGIC,
GI_DOUBLE_MAGIC,
GI_DOUBLE_DEFENSE,
GI_BOTTLE_WITH_RED_POTION,
GI_BOTTLE_WITH_GREEN_POTION,
GI_BOTTLE_WITH_BLUE_POTION,
GI_BOTTLE_WITH_FAIRY,
GI_BOTTLE_WITH_FISH,
GI_BOTTLE_WITH_BLUE_FIRE,
GI_BOTTLE_WITH_BUGS,
GI_BOTTLE_WITH_POE,
GI_BOTTLE_WITH_BIG_POE,
/* 0x84 */ GI_MAX
} GetItemID;
typedef enum {
@ -501,7 +550,18 @@ typedef enum {
/* 0x72 */ GID_BULLET_BAG_50,
/* 0x73 */ GID_SWORD_KOKIRI,
/* 0x74 */ GID_SKULL_TOKEN_2,
/* 0x75 */ GID_MAXIMUM
/* 0x74 */ GID_KOKIRI_EMERALD,
/* 0x74 */ GID_GORON_RUBY,
/* 0x74 */ GID_ZORA_SAPPHIRE,
/* 0x75 */ GID_SONG_GENERIC,
/* 0x76 */ GID_SONG_ZELDA,
/* 0x77 */ GID_SONG_EPONA,
/* 0x78 */ GID_SONG_SARIA,
/* 0x79 */ GID_SONG_SUN,
/* 0x7A */ GID_SONG_TIME,
/* 0x7B */ GID_SONG_STORM,
/* 0x7C */ GID_MAXIMUM
} GetItemDrawID;
typedef enum {

View File

@ -481,7 +481,7 @@ typedef struct Player {
/* 0x042D */ s8 doorDirection;
/* 0x042E */ s16 doorTimer;
/* 0x0430 */ Actor* doorActor;
/* 0x0434 */ s8 getItemId;
/* 0x0434 */ s16 getItemId;
/* 0x0436 */ u16 getItemDirection;
/* 0x0438 */ Actor* interactRangeActor;
/* 0x043C */ s8 mountSide;
@ -614,4 +614,4 @@ typedef struct Player {
/* 0x0A88 */ Vec3f unk_A88; // previous body part 0 position
} Player; // size = 0xA94
#endif
#endif

View File

@ -3,6 +3,7 @@
#include "ultra64.h"
#include "z64math.h"
#include <randomizerTypes.h>
typedef struct {
/* 0x00 */ u8 buttonItems[8];
@ -60,6 +61,21 @@ typedef struct {
/* 0x24 */ s32 tempCollectFlags;
} FaroresWindData; // size = 0x28
typedef struct {
RandomizerCheck check;
RandomizerGet get;
} ItemLocationRando;
typedef struct {
RandomizerCheck check;
char hintText[200];
} HintLocationRando;
typedef struct {
RandomizerSettingKey key;
u8 value;
} RandoSetting;
typedef struct {
/* 0x0000 */ s32 entranceIndex; // start of `save` substruct, originally called "memory"
/* 0x0004 */ s32 linkAge; // 0: Adult; 1: Child
@ -157,6 +173,17 @@ typedef struct {
/* 0x1420 */ s16 worldMapArea;
/* 0x1422 */ s16 sunsSongState; // controls the effects of suns song
/* 0x1424 */ s16 healthAccumulator;
RandoSetting randoSettings[300];
ItemLocationRando itemLocations[500];
HintLocationRando hintLocations[50];
char childAltarText[250];
char adultAltarText[750];
char ganonHintText[150];
char ganonText[250];
u8 seedIcons[5];
u8 dungeonsDone[8];
u8 trialsDone[6];
u8 temporaryWeapon;
} SaveContext; // size = 0x1428
typedef enum {

View File

@ -7,7 +7,7 @@
<key>CFBundleName</key>
<string>Ship of Harkinian</string>
<key>CFBundleExecutable</key>
<string>launcher.sh</string>
<string>soh</string>
<key>CFBundleGetInfoString</key>
<string>2.0.0</string>
<key>CFBundleIconFile</key>

View File

@ -1,26 +0,0 @@
#import <Foundation/Foundation.h>
int main(void) {
NSString *appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
//If there isn't an App Support Directory yet ...
if (![[NSFileManager defaultManager] fileExistsAtPath:appSupportDir isDirectory:NULL]) {
NSError *error = nil;
//Create one
if (![[NSFileManager defaultManager] createDirectoryAtPath:appSupportDir withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"%@", error.localizedDescription);
}
else {
// *** OPTIONAL *** Mark the directory as excluded from iCloud backups
NSURL *url = [NSURL fileURLWithPath:appSupportDir];
if (![url setResourceValue:@YES
forKey:NSURLIsExcludedFromBackupKey
error:&error])
{
NSLog(@"Error excluding %@ from backup %@", url.lastPathComponent, error.localizedDescription);
}
else {
NSLog(@"Yay");
}
}
}
printf("%s\n", [appSupportDir UTF8String]);
}

View File

@ -1,9 +0,0 @@
#!/bin/bash
APPPATH="${0%/*}"
cd "${APPPATH}"
APPPATH=$(pwd)
APPSUPPORT=$(./appsupport)
mkdir -p "${APPSUPPORT}/com.shipofharkinian.soh"
cd "${APPSUPPORT}/com.shipofharkinian.soh"
cp "${APPPATH}/oot.otr" .
${APPPATH}/soh

1000
soh/randomizerTypes.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
#include <Lib/spdlog/include/spdlog/spdlog.h>
void GenerateRandomizer() {
int ret = Playthrough::Playthrough_Init(std::hash<std::string>{}(Settings::seed));
if (ret < 0) {
if (ret == -1) { // Failed to generate after 5 tries
SPDLOG_ERROR(
"\n\nFailed to generate after 5 tries.\nPress B to go back to the menu.\nA different seed might be "
"successful.");
return;
} else {
SPDLOG_ERROR("\n\nError %d with fill.\nPress Select to exit or B to go back to the menu.\n", ret);
return;
}
}
const auto& randomizerHash = GetRandomizerHash();
}

View File

@ -143,7 +143,7 @@
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>INCLUDE_GAME_PRINTF;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions)GLEW_STATIC </PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -181,6 +181,61 @@
<ItemGroup>
<ClCompile Include="soh\Enhancements\cosmetics\CosmeticsEditor.cpp" />
<ClCompile Include="soh\Enhancements\debugger\actorViewer.cpp" />
<ClCompile Include="soh\Enhancements\gfx.c" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_exclude_dungeon.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_exclude_overworld.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_item.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_castle_town.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_kakariko.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_shadow_temple.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_spirit_temple.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_forest_temple.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_gerudo_training_grounds.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_deku_tree.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_ice_cavern.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_fire_temple.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_lost_woods.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_bottom_of_the_well.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_hyrule_field.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_gerudo_valley.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_ganons_castle.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_water_temple.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_zoras_domain.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_jabujabus_belly.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_dodongos_cavern.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_death_mountain.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\cosmetics.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\custom_messages.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\debug.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\dungeon.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\entrance.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\fill.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hints.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_list.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_location.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_pool.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\logic.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\menu.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\music.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\patch.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\playthrough.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\preset.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\random.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\rando_main.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\settings.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\setting_descriptions.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\shops.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\sound_effects.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\spoiler_log.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\starting_inventory.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\tinyxml2.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\trial.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\3drando\utils.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\randomizer.cpp" />
<ClCompile Include="soh\Enhancements\randomizer\randomizer_item_tracker.cpp" />
<ClCompile Include="soh\frame_interpolation.cpp" />
<ClCompile Include="soh\Enhancements\bootcommands.c" />
<ClCompile Include="soh\Enhancements\debugconsole.cpp" />
@ -886,10 +941,50 @@
<ClCompile Include="src\overlays\misc\ovl_kaleido_scope\z_lmap_mark.c" />
<ClCompile Include="src\overlays\misc\ovl_kaleido_scope\z_lmap_mark_data.c" />
<ClCompile Include="src\overlays\misc\ovl_map_mark_data\z_map_mark_data.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="soh\Enhancements\cosmetics\CosmeticsEditor.h" />
<ClInclude Include="soh\Enhancements\debugger\actorViewer.h" />
<ClCompile Include="soh\Enhancements\randomizer\randomizer.h" />
<ClInclude Include="soh\Enhancements\gfx.h" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\category.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\cosmetics.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\custom_messages.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\debug.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\dungeon.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\entrance.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\fill.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\hints.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\hint_list.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_list.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_location.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_pool.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\keys.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\location_access.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\logic.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\menu.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\music.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\patch.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\playthrough.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\pool_functions.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\preset.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\random.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\randomizer.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\rando_main.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\settings.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\setting_descriptions.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\shops.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\sound_effects.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\spoiler_log.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\starting_inventory.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\text.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\tinyxml2.h" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\trial.hpp" />
<ClInclude Include="soh\Enhancements\randomizer\3drando\utils.hpp" />
<ClInclude Include="randomizerTypes.h" />
<ClInclude Include="soh\Enhancements\randomizer\randomizer_item_tracker.h" />
<ClInclude Include="soh\frame_interpolation.h" />
<ClInclude Include="include\alloca.h" />
<ClInclude Include="include\bgm.h" />
@ -944,7 +1039,6 @@
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h" />
<ClInclude Include="soh\Enhancements\debugger\ImGuiHelpers.h" />
<ClInclude Include="soh\gameconsole.h" />
<ClInclude Include="soh\Lib\nlohmann\json.hpp" />
<ClInclude Include="soh\OTRAudio.h" />
<ClInclude Include="soh\OTRGlobals.h" />
<ClInclude Include="soh\SaveManager.h" />

View File

@ -82,12 +82,6 @@
<Filter Include="Source Files\soh\Enhancements\debugger">
<UniqueIdentifier>{04fc1c52-49ff-48e2-ae23-2c00867374f8}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\soh\Lib">
<UniqueIdentifier>{dbcf07c4-80b1-4c88-ac54-2bbdd8f53ee4}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\soh\Lib\nlohmann">
<UniqueIdentifier>{9c880c8e-492b-48f6-b230-1fd269ea74b1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\boot\assert.c">
@ -2211,6 +2205,174 @@
<ClCompile Include="soh\Enhancements\cosmetics\CosmeticsEditor.cpp">
<Filter>Header Files\include</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_exclude_dungeon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_exclude_overworld.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list\hint_list_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_bottom_of_the_well.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_castle_town.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_death_mountain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_deku_tree.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_dodongos_cavern.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_fire_temple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_forest_temple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_ganons_castle.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_gerudo_training_grounds.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_gerudo_valley.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_hyrule_field.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_ice_cavern.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_jabujabus_belly.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_kakariko.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_lost_woods.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_shadow_temple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_spirit_temple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_water_temple.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access\locacc_zoras_domain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\cosmetics.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\custom_messages.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\debug.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\dungeon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\entrance.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\fill.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\hints.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\hint_list.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_list.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_location.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\item_pool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\location_access.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\logic.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\menu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\music.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\patch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\playthrough.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\preset.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\random.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\rando_main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\setting_descriptions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\shops.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\sound_effects.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\spoiler_log.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\starting_inventory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\tinyxml2.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\trial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\3drando\utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\randomizer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\randomizer.h">
<Filter>Header Files</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\gfx.c">
<Filter>Header Files\soh\Enhancements</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\randomizer\randomizer_item_tracker.cpp">
<Filter>Source Files\src</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\overlays\actors\ovl_kaleido_scope\z_kaleido_scope.h">
@ -3779,15 +3941,126 @@
<ClInclude Include="soh\OTRAudio.h">
<Filter>Source Files\soh</Filter>
</ClInclude>
<ClInclude Include="soh\Lib\nlohmann\json.hpp">
<Filter>Source Files\soh\Lib\nlohmann</Filter>
</ClInclude>
<ClInclude Include="soh\SaveManager.h">
<Filter>Source Files\soh</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\cosmetics\CosmeticsEditor.h">
<Filter>Header Files\include</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\category.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\cosmetics.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\custom_messages.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\debug.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\dungeon.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\entrance.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\fill.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\hints.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\hint_list.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\item.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_list.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_location.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\item_pool.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\keys.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\location_access.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\logic.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\menu.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\music.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\patch.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\playthrough.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\pool_functions.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\preset.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\random.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\randomizer.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\rando_main.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\settings.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\setting_descriptions.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\shops.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\sound_effects.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\spoiler_log.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\starting_inventory.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\text.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\tinyxml2.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\trial.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\3drando\utils.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="randomizerTypes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\gfx.h">
<Filter>Header Files\soh\Enhancements</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\randomizer\randomizer_item_tracker.h">
<Filter>Source Files\src</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="SHIPOFHARKINIAN.ico" />
@ -3800,4 +4073,4 @@
<ItemGroup>
<None Include="include\macro.inc" />
</ItemGroup>
</Project>
</Project>

View File

@ -5,6 +5,7 @@
#include <ultra64.h>
#include <functions.h>
#include <variables.h>
#include <string.h>
#include <stdarg.h>
#include <z64.h>
#include <ultra64/gbi.h>
@ -19,7 +20,7 @@ extern BootCommandFunc BootCommands_Command_LoadFileSelect(char** argv, s32 argc
static BootCommand sCommands[] = { { "--skiplogo", BootCommands_Command_SkipLogo },
{ "--loadfileselect", BootCommands_Command_LoadFileSelect } };
void BootCommands_Init()
void BootCommands_Init()
{
CVar_RegisterS32("gDisableLOD", 0);
CVar_RegisterS32("gDebugEnabled", 0);
@ -29,7 +30,6 @@ void BootCommands_Init()
CVar_RegisterS32("gHoverFishing", 0);
CVar_RegisterS32("gN64WeirdFrames", 0);
CVar_RegisterS32("gBombchusOOB", 0);
CVar_RegisterS32("gRumbleEnabled", 0);
CVar_RegisterS32("gUniformLR", 0);
CVar_RegisterS32("gTwoHandedIdle", 0);
CVar_RegisterS32("gDekuNutUpgradeFix", 0);

View File

@ -79,6 +79,67 @@ ImVec4 menu_map_colors;
ImVec4 menu_quest_colors;
ImVec4 menu_save_colors;
ImVec4 menu_gameover_colors;*/
const char* RainbowColorCvarList[] = {
//This is the list of possible CVars that has rainbow effect.
"gTunic_Kokiri_", "gTunic_Goron_", "gTunic_Zora_",
"gFireArrowCol", "gIceArrowCol", "gTunic_Zora_",
"gFireArrowColEnv", "gIceArrowColEnv", "gLightArrowColEnv",
"gCCHeartsPrim", "gDDCCHeartsPrim", "gLightArrowCol", "gCCDDHeartsPrim",
"gCCABtnPrim", "gCCBBtnPrim", "gCCCBtnPrim", "gCCStartBtnPrim",
"gCCCUBtnPrim", "gCCCLBtnPrim", "gCCCRBtnPrim", "gCCCDBtnPrim", "gCCDpadPrim",
"gCCMagicBorderNormPrim", "gCCMagicBorderPrim", "gCCMagicPrim", "gCCMagicUsePrim",
"gCCMinimapPrim", "gCCMinimapDGNPrim", "gCCMinimapCPPrim", "gCCMinimapLEPrim",
"gCCRupeePrim", "gCCKeysPrim", "gDog1Col", "gDog2Col", "gCCVSOAPrim",
"gKeese1_Ef_Prim","gKeese2_Ef_Prim","gKeese1_Ef_Env","gKeese2_Ef_Env",
"gDF_Col", "gDF_Env",
"gNL_Diamond_Col", "gNL_Diamond_Env", "gNL_Orb_Col", "gNL_Orb_Env",
"gTrailCol", "gCharged1Col", "gCharged1ColEnv", "gCharged2Col", "gCharged2ColEnv",
"gCCFileChoosePrim", "gCCFileChooseTextPrim", "gCCEquipmentsPrim", "gCCItemsPrim",
"gCCMapsPrim", "gCCQuestsPrim", "gCCSavePrim", "gCCGameoverPrim",
};
void LoadRainbowColor(bool& open) {
u8 arrayLength = sizeof(RainbowColorCvarList) / sizeof(*RainbowColorCvarList);
for (u8 s = 0; s < arrayLength; s++) {
std::string cvarName = RainbowColorCvarList[s];
std::string Cvar_Red = cvarName;
Cvar_Red += "R";
std::string Cvar_Green = cvarName;
Cvar_Green += "G";
std::string Cvar_Blue = cvarName;
Cvar_Blue += "B";
std::string Cvar_RBM = cvarName;
Cvar_RBM += "RBM";
std::string RBM_HUE = cvarName;
RBM_HUE += "Hue";
f32 Canon = 10.f * s;
ImVec4 NewColor;
const f32 deltaTime = 1.0f / ImGui::GetIO().Framerate;
f32 hue = CVar_GetFloat(RBM_HUE.c_str(), 0.0f);
f32 newHue = hue + CVar_GetS32("gColorRainbowSpeed", 1) * 36.0f * deltaTime;
if (newHue >= 360)
newHue = 0;
CVar_SetFloat(RBM_HUE.c_str(), newHue);
f32 current_hue = CVar_GetFloat(RBM_HUE.c_str(), 0);
u8 i = current_hue / 60 + 1;
u8 a = (-current_hue / 60.0f + i) * 255;
u8 b = (current_hue / 60.0f + (1 - i)) * 255;
switch (i) {
case 1: NewColor.x = 255; NewColor.y = b; NewColor.z = 0; break;
case 2: NewColor.x = a; NewColor.y = 255; NewColor.z = 0; break;
case 3: NewColor.x = 0; NewColor.y = 255; NewColor.z = b; break;
case 4: NewColor.x = 0; NewColor.y = a; NewColor.z = 255; break;
case 5: NewColor.x = b; NewColor.y = 0; NewColor.z = 255; break;
case 6: NewColor.x = 255; NewColor.y = 0; NewColor.z = a; break;
}
if (CVar_GetS32(Cvar_RBM.c_str(), 0) != 0) {
CVar_SetS32(Cvar_Red.c_str(), SohImGui::ClampFloatToInt(NewColor.x, 0, 255));
CVar_SetS32(Cvar_Green.c_str(), SohImGui::ClampFloatToInt(NewColor.y, 0, 255));
CVar_SetS32(Cvar_Blue.c_str(), SohImGui::ClampFloatToInt(NewColor.z, 0, 255));
}
}
}
void Table_InitHeader(bool has_header = true) {
if (has_header) {
ImGui::TableHeadersRow();
@ -117,28 +178,28 @@ void Draw_Npcs(){
ImGui::TableSetupColumn("Outer colors##Navi", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort, TablesCellsWidth/2);
Table_InitHeader();
Draw_HelpIcon("Inner color for Navi (idle flying around)");
SohImGui::EnhancementColor("Navi Idle", "gNavi_Idle_Inner_", navi_idle_i_col, ImVec4(255, 255, 255, 255), false);
SohImGui::EnhancementColor("Navi Idle (Primary)", "gNavi_Idle_Inner_", navi_idle_i_col, ImVec4(255, 255, 255, 255), false);
Table_NextCol();
Draw_HelpIcon("Outer color for Navi (idle flying around)");
SohImGui::EnhancementColor("Navi Idle", "gNavi_Idle_Outer_", navi_idle_o_col, ImVec4(0, 0, 255, 255), false);
SohImGui::EnhancementColor("Navi Idle (Secondary)", "gNavi_Idle_Outer_", navi_idle_o_col, ImVec4(0, 0, 255, 255), false);
Table_NextLine();
Draw_HelpIcon("Inner color for Navi (when Navi fly around NPCs)");
SohImGui::EnhancementColor("Navi NPC", "gNavi_NPC_Inner_", navi_npc_i_col, ImVec4(150, 150, 255, 255), false);
SohImGui::EnhancementColor("Navi NPC (Primary)", "gNavi_NPC_Inner_", navi_npc_i_col, ImVec4(150, 150, 255, 255), false);
Table_NextCol();
Draw_HelpIcon("Outer color for Navi (when Navi fly around NPCs)");
SohImGui::EnhancementColor("Navi NPC", "gNavi_NPC_Outer_", navi_npc_o_col, ImVec4(150, 150, 255, 255), false);
SohImGui::EnhancementColor("Navi NPC (Secondary)", "gNavi_NPC_Outer_", navi_npc_o_col, ImVec4(150, 150, 255, 255), false);
Table_NextLine();
Draw_HelpIcon("Inner color for Navi (when Navi fly around Enemies or Bosses)");
SohImGui::EnhancementColor("Navi Enemy", "gNavi_Enemy_Inner_", navi_enemy_i_col, ImVec4(255, 255, 0, 255), false);
SohImGui::EnhancementColor("Navi Enemy (Primary)", "gNavi_Enemy_Inner_", navi_enemy_i_col, ImVec4(255, 255, 0, 255), false);
Table_NextCol();
Draw_HelpIcon("Outer color for Navi (when Navi fly around Enemies or Bosses)");
SohImGui::EnhancementColor("Navi Enemy", "gNavi_Enemy_Outer_", navi_enemy_o_col, ImVec4(220, 155, 0, 255), false);
SohImGui::EnhancementColor("Navi Enemy (Secondary)", "gNavi_Enemy_Outer_", navi_enemy_o_col, ImVec4(220, 155, 0, 255), false);
Table_NextLine();
Draw_HelpIcon("Inner color for Navi (when Navi fly around props (signs etc))");
SohImGui::EnhancementColor("Navi Prop", "gNavi_Prop_Inner_", navi_prop_i_col, ImVec4(0, 255, 0, 255), false);
SohImGui::EnhancementColor("Navi Prop (Primary)", "gNavi_Prop_Inner_", navi_prop_i_col, ImVec4(0, 255, 0, 255), false);
Table_NextCol();
Draw_HelpIcon("Outer color for Navi (when Navi fly around props (signs etc))");
SohImGui::EnhancementColor("Navi Prop", "gNavi_Prop_Outer_", navi_prop_o_col, ImVec4(0, 255, 0, 255), false);
SohImGui::EnhancementColor("Navi Prop (Secondary)", "gNavi_Prop_Outer_", navi_prop_o_col, ImVec4(0, 255, 0, 255), false);
ImGui::EndTable();
}
SohImGui::EnhancementCheckbox("Custom colors for Keese", "gUseKeeseCol");
@ -151,10 +212,10 @@ void Draw_Npcs(){
SohImGui::EnhancementColor("Fire Primary color", "gKeese1_Ef_Prim", Keese1_primcol, ImVec4(255, 255, 100, 255));
Table_NextCol();
Draw_HelpIcon("Affects the primary color of the Ice itself of the Keese");
SohImGui::EnhancementColor("Fire Primary color", "gKeese2_Ef_Prim", Keese2_primcol, ImVec4(100, 200, 255, 255));
SohImGui::EnhancementColor("Ice Primary color", "gKeese2_Ef_Prim", Keese2_primcol, ImVec4(100, 200, 255, 255));
Table_NextLine();
Draw_HelpIcon("Affects the secondary color of the Fire itself of the Keese");
SohImGui::EnhancementColor("Ice Secondary color", "gKeese1_Ef_Env", Keese1_envcol, ImVec4(255, 50, 0, 255));
SohImGui::EnhancementColor("Fire Secondary color", "gKeese1_Ef_Env", Keese1_envcol, ImVec4(255, 50, 0, 255));
Table_NextCol();
Draw_HelpIcon("Affects the secondary color of the Ice itself of the Keese");
SohImGui::EnhancementColor("Ice Secondary color", "gKeese2_Ef_Env", Keese2_envcol, ImVec4(0, 0, 255, 255));
@ -258,11 +319,11 @@ void Draw_ItemsSkills(){
SohImGui::EnhancementColor("Level 2 color", "gCharged2ColEnv", charged2_colenv, ImVec4(255,100,0,255));
ImGui::EndTable();
}
SohImGui::EnhancementCheckbox("Custom sword trails color", "gUseTrailsCol");
SohImGui::EnhancementCheckbox("Custom trails color", "gUseTrailsCol");
if (CVar_GetS32("gUseTrailsCol",0) && ImGui::BeginTable("tabletrails", 1, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) {
ImGui::TableSetupColumn("Custom Swords trails", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort, TablesCellsWidth);
ImGui::TableSetupColumn("Custom Trails", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoSort, TablesCellsWidth);
Table_InitHeader();
Draw_HelpIcon("Affects Slash trails color");
Draw_HelpIcon("Affects Swords slash, boomerang and Bombchu trails color");
SohImGui::EnhancementColor("Trails color", "gTrailCol", trailscol, ImVec4(255,255,255,255));
SohImGui::EnhancementSliderInt("Trails duration: %dx", "##TrailsMul", "gTrailDurantion", 1, 5, "");
SohImGui::Tooltip("The longer the trails the weirder it become");
@ -1044,6 +1105,10 @@ void DrawCosmeticsEditor(bool& open) {
ImGui::End();
}
void InitCosmeticsEditor() {
//This allow to hide a window without disturbing the player nor adding things in menu
//LoadRainbowColor() will this way run in background once it's window is activated
//ImGui::SetNextItemWidth(0.0f);
SohImGui::AddWindow("Cosmetics", "Rainbowfunction", LoadRainbowColor, true, true);
//Draw the bar in the menu.
SohImGui::AddWindow("Cosmetics", "Cosmetics Editor", DrawCosmeticsEditor);
}
}

View File

@ -1,3 +1,4 @@
#pragma once
void InitCosmeticsEditor();//Init the menu itself
void LoadRainbowColor();

View File

@ -16,6 +16,7 @@
#include <Utils/StringHelper.h>
#include <Utils/File.h>
#include "Window.h"
#include "Lib/ImGui/imgui_internal.h"
#undef PATH_HACK
#undef Path
@ -315,7 +316,7 @@ static bool SaveStateHandler(const std::vector<std::string>& args) {
unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot();
const SaveStateReturn rtn = OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::SAVE });
switch (rtn) {
switch (rtn) {
case SaveStateReturn::SUCCESS:
INFO("[SOH] Saved state to slot %u", slot);
return CMD_SUCCESS;
@ -329,7 +330,7 @@ static bool SaveStateHandler(const std::vector<std::string>& args) {
static bool LoadStateHandler(const std::vector<std::string>& args) {
unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot();
const SaveStateReturn rtn = OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::LOAD });
switch (rtn) {
case SaveStateReturn::SUCCESS:
INFO("[SOH] Loaded state from slot %u", slot);
@ -342,7 +343,7 @@ static bool LoadStateHandler(const std::vector<std::string>& args) {
return CMD_FAILED;
case SaveStateReturn::FAIL_WRONG_GAMESTATE:
ERROR("[SOH] Can not load a state outside of \"GamePlay\"");
return CMD_FAILED;
return CMD_FAILED;
}
}
@ -360,7 +361,7 @@ static bool StateSlotSelectHandler(const std::vector<std::string>& args) {
ERROR("[SOH] SaveState slot value must be a number.");
return CMD_FAILED;
}
if (slot < 0) {
ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2");
return CMD_FAILED;
@ -498,10 +499,10 @@ template <typename Numeric> bool is_number(const std::string& s) {
return ((std::istringstream(s) >> n >> std::ws).eof());
}
void DebugConsole_LoadCVars()
{
if (File::Exists("cvars.cfg")) {
const auto lines = File::ReadAllLines("cvars.cfg");
void DebugConsole_LoadLegacyCVars() {
auto cvarsConfig = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("cvars.cfg");
if (File::Exists(cvarsConfig)) {
const auto lines = File::ReadAllLines(cvarsConfig);
for (const std::string& line : lines) {
std::vector<std::string> cfg = StringHelper::Split(line, " = ");
@ -509,7 +510,7 @@ void DebugConsole_LoadCVars()
if (cfg.size() < 2) continue;
if (cfg[1].find("\"") != std::string::npos) {
std::string value(cfg[1]);
value.erase(std::ranges::remove(value, '\"').begin(), value.end());
value.erase(std::remove(value.begin(), value.end(), '\"'), value.end());
CVar_SetString(cfg[0].c_str(), ImStrdup(value.c_str()));
}
if (is_number<float>(cfg[1])) {
@ -519,21 +520,58 @@ void DebugConsole_LoadCVars()
CVar_SetS32(cfg[0].c_str(), std::stoi(cfg[1]));
}
}
fs::remove(cvarsConfig);
}
}
void DebugConsole_LoadCVars() {
std::shared_ptr<Mercury> pConf = Ship::GlobalCtx2::GetInstance()->GetConfig();
pConf->reload();
for (const auto& item : pConf->rjson["CVars"].items()) {
auto value = item.value();
switch (value.type()) {
case nlohmann::detail::value_t::array:
break;
case nlohmann::detail::value_t::string:
CVar_SetString(item.key().c_str(), value.get<std::string>().c_str());
break;
case nlohmann::detail::value_t::boolean:
CVar_SetS32(item.key().c_str(), value.get<bool>());
break;
case nlohmann::detail::value_t::number_unsigned:
case nlohmann::detail::value_t::number_integer:
CVar_SetS32(item.key().c_str(), value.get<int>());
break;
case nlohmann::detail::value_t::number_float:
CVar_SetFloat(item.key().c_str(), value.get<float>());
break;
default: ;
}
if (item.key() == "gOpenMenuBar") {
int bp = 0;
}
}
DebugConsole_LoadLegacyCVars();
}
void DebugConsole_SaveCVars()
{
std::string output;
std::shared_ptr<Mercury> pConf = Ship::GlobalCtx2::GetInstance()->GetConfig();
for (const auto &cvar : cvars) {
if (cvar.second->type == CVAR_TYPE_STRING)
output += StringHelper::Sprintf("%s = \"%s\"\n", cvar.first.c_str(), cvar.second->value.valueStr);
const std::string key = StringHelper::Sprintf("CVars.%s", cvar.first.c_str());
if (cvar.second->type == CVAR_TYPE_STRING && cvar.second->value.valueStr != nullptr)
pConf->setString(key, std::string(cvar.second->value.valueStr));
else if (cvar.second->type == CVAR_TYPE_S32)
output += StringHelper::Sprintf("%s = %i\n", cvar.first.c_str(), cvar.second->value.valueS32);
pConf->setInt(key, cvar.second->value.valueS32);
else if (cvar.second->type == CVAR_TYPE_FLOAT)
output += StringHelper::Sprintf("%s = %f\n", cvar.first.c_str(), cvar.second->value.valueFloat);
pConf->setFloat(key, cvar.second->value.valueFloat);
}
File::WriteAllText("cvars.cfg", output);
pConf->save();
}

View File

@ -194,9 +194,9 @@ void CreateSphereFace(std::vector<std::tuple<size_t, size_t, size_t>>& faces, in
// Create 3 new verticies at the midpoints
Vec3f vs[3] = {
Vec3f((v0.n.ob[0] + v1.n.ob[0]) / 2.0f, (v0.n.ob[1] + v1.n.ob[1]) / 2.0f, (v0.n.ob[2] + v1.n.ob[2]) / 2.0f),
Vec3f((v1.n.ob[0] + v2.n.ob[0]) / 2.0f, (v1.n.ob[1] + v2.n.ob[1]) / 2.0f, (v1.n.ob[2] + v2.n.ob[2]) / 2.0f),
Vec3f((v2.n.ob[0] + v0.n.ob[0]) / 2.0f, (v2.n.ob[1] + v0.n.ob[1]) / 2.0f, (v2.n.ob[2] + v0.n.ob[2]) / 2.0f)
Vec3f{(v0.n.ob[0] + v1.n.ob[0]) / 2.0f, (v0.n.ob[1] + v1.n.ob[1]) / 2.0f, (v0.n.ob[2] + v1.n.ob[2]) / 2.0f},
Vec3f{(v1.n.ob[0] + v2.n.ob[0]) / 2.0f, (v1.n.ob[1] + v2.n.ob[1]) / 2.0f, (v1.n.ob[2] + v2.n.ob[2]) / 2.0f},
Vec3f{(v2.n.ob[0] + v0.n.ob[0]) / 2.0f, (v2.n.ob[1] + v0.n.ob[1]) / 2.0f, (v2.n.ob[2] + v0.n.ob[2]) / 2.0f}
};
// Normalize vertex positions so they are on the sphere
@ -221,20 +221,20 @@ void CreateSphereData() {
float d = (1.0f + sqrtf(5.0f)) / 2.0f;
// Create the 12 starting verticies, 4 on each rectangle
base.emplace_back(-1, d, 0);
base.emplace_back(1, d, 0);
base.emplace_back(-1, -d, 0);
base.emplace_back(1, -d, 0);
base.emplace_back(Vec3f({-1, d, 0}));
base.emplace_back(Vec3f({1, d, 0}));
base.emplace_back(Vec3f({-1, -d, 0}));
base.emplace_back(Vec3f({1, -d, 0}));
base.emplace_back(0, -1, d);
base.emplace_back(0, 1, d);
base.emplace_back(0, -1, -d);
base.emplace_back(0, 1, -d);
base.emplace_back(Vec3f({0, -1, d}));
base.emplace_back(Vec3f({0, 1, d}));
base.emplace_back(Vec3f({0, -1, -d}));
base.emplace_back(Vec3f({0, 1, -d}));
base.emplace_back(d, 0, -1);
base.emplace_back(d, 0, 1);
base.emplace_back(-d, 0, -1);
base.emplace_back(-d, 0, 1);
base.emplace_back(Vec3f({d, 0, -1}));
base.emplace_back(Vec3f({d, 0, 1}));
base.emplace_back(Vec3f({-d, 0, -1}));
base.emplace_back(Vec3f({-d, 0, 1}));
// Normalize verticies so they are on the unit sphere
for (Vec3f& v : base) {

View File

@ -130,6 +130,9 @@ std::map<uint32_t, ItemMapEntry> itemMapping = {
ITEM_MAP_ENTRY(ITEM_COMPASS),
ITEM_MAP_ENTRY(ITEM_DUNGEON_MAP),
ITEM_MAP_ENTRY(ITEM_KEY_SMALL),
ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER),
ITEM_MAP_ENTRY(ITEM_MAGIC_SMALL),
ITEM_MAP_ENTRY(ITEM_MAGIC_LARGE)
};
// Maps entries in the GS flag array to the area name it represents
@ -185,17 +188,18 @@ typedef struct {
// Maps quest items ids to info for use in ImGui
std::map<uint32_t, QuestMapEntry> questMapping = {
QUEST_MAP_ENTRY(QUEST_MEDALLION_FOREST, gForestMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_FIRE, gFireMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_WATER, gWaterMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SPIRIT, gSpiritMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SHADOW, gShadowMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_LIGHT, gLightMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_KOKIRI_EMERALD, gKokiriEmeraldIconTex),
QUEST_MAP_ENTRY(QUEST_GORON_RUBY, gGoronRubyIconTex),
QUEST_MAP_ENTRY(QUEST_ZORA_SAPPHIRE, gZoraSapphireIconTex),
QUEST_MAP_ENTRY(QUEST_STONE_OF_AGONY, gStoneOfAgonyIconTex),
QUEST_MAP_ENTRY(QUEST_GERUDO_CARD, gGerudosCardIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_FOREST, dgForestMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_FIRE, dgFireMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_WATER, dgWaterMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SPIRIT, dgSpiritMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SHADOW, dgShadowMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_LIGHT, dgLightMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_KOKIRI_EMERALD, dgKokiriEmeraldIconTex),
QUEST_MAP_ENTRY(QUEST_GORON_RUBY, dgGoronRubyIconTex),
QUEST_MAP_ENTRY(QUEST_ZORA_SAPPHIRE, dgZoraSapphireIconTex),
QUEST_MAP_ENTRY(QUEST_STONE_OF_AGONY, dgStoneOfAgonyIconTex),
QUEST_MAP_ENTRY(QUEST_GERUDO_CARD, dgGerudosCardIconTex),
QUEST_MAP_ENTRY(QUEST_SKULL_TOKEN, dgGoldSkulltulaIconTex),
};
typedef struct {
@ -212,12 +216,12 @@ typedef struct {
// Maps song ids to info for use in ImGui
std::array<SongMapEntry, 12> songMapping = { {
SONG_MAP_ENTRY(QUEST_SONG_LULLABY, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_EPONA, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_SARIA, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_SUN, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_TIME, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_STORMS, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_LULLABY, 224, 107, 255),
SONG_MAP_ENTRY(QUEST_SONG_EPONA, 255, 195, 60),
SONG_MAP_ENTRY(QUEST_SONG_SARIA, 127, 255, 137),
SONG_MAP_ENTRY(QUEST_SONG_SUN, 255, 255, 60),
SONG_MAP_ENTRY(QUEST_SONG_TIME, 119, 236, 255),
SONG_MAP_ENTRY(QUEST_SONG_STORMS, 165, 165, 165),
SONG_MAP_ENTRY(QUEST_SONG_MINUET, 150, 255, 100),
SONG_MAP_ENTRY(QUEST_SONG_BOLERO, 255, 80, 40),
SONG_MAP_ENTRY(QUEST_SONG_SERENADE, 100, 150, 255),
@ -226,6 +230,27 @@ std::array<SongMapEntry, 12> songMapping = { {
SONG_MAP_ENTRY(QUEST_SONG_PRELUDE, 255, 240, 100),
} };
#define VANILLA_SONG_MAP_ENTRY(id, r, g, b) \
{ \
id, #id "_Vanilla", #id "_Vanilla_Faded", ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f) \
}
// Maps song ids to info for use in ImGui
std::array<SongMapEntry, 12> vanillaSongMapping = { {
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_LULLABY, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_EPONA, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_SARIA, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_SUN, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_TIME, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_STORMS, 255, 255, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_MINUET, 150, 255, 100),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_BOLERO, 255, 80, 40),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_SERENADE, 100, 150, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_REQUIEM, 255, 160, 0),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_NOCTURNE, 255, 100, 255),
VANILLA_SONG_MAP_ENTRY(QUEST_SONG_PRELUDE, 255, 240, 100),
} };
// Encapsulates what is drawn by the passed-in function within a border
template<typename T>
void DrawGroupWithBorder(T&& drawFunc) {
@ -1610,4 +1635,10 @@ void InitSaveEditor() {
fadedCol.w = 0.3f;
SohImGui::LoadResource(entry.nameFaded, gSongNoteTex, fadedCol);
}
for (const auto& entry : vanillaSongMapping) {
SohImGui::LoadResource(entry.name, gSongNoteTex, entry.color);
ImVec4 fadedCol = entry.color;
fadedCol.w = 0.3f;
SohImGui::LoadResource(entry.nameFaded, gSongNoteTex, fadedCol);
}
}

View File

@ -0,0 +1,63 @@
#include "gfx.h"
#include "global.h"
#include "z64.h"
extern GlobalContext* gGlobalCtx;
/**
* Simple wrapper to load a texture to be drawn.
*/
void sprite_load(sprite_t* sprite) {
OPEN_DISPS(gGlobalCtx->state.gfxCtx);
if (sprite->im_siz == G_IM_SIZ_16b) {
gDPLoadTextureBlock(
OVERLAY_DISP++,
sprite->tex,
sprite->im_fmt,
G_IM_SIZ_16b, // @TEMP until I figure out how to use sprite->im_siz
sprite->width, sprite->height,
0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMASK, G_TX_NOMASK,
G_TX_NOLOD, G_TX_NOLOD
);
} else {
gDPLoadTextureBlock(
OVERLAY_DISP++,
sprite->tex,
sprite->im_fmt,
G_IM_SIZ_32b, // @TEMP until I figure out how to use sprite->im_siz
sprite->width, sprite->height,
0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMASK, G_TX_NOMASK,
G_TX_NOLOD, G_TX_NOLOD
);
}
CLOSE_DISPS(gGlobalCtx->state.gfxCtx);
}
/**
* Simple wrapper to draw a sprite/texture on the screen.
* sprite_load needs to be ran right before this.
*/
void sprite_draw(sprite_t* sprite, int left, int top, int width, int height) {
int width_factor = (1 << 10) * sprite->width / width;
int height_factor = (1 << 10) * sprite->height / height;
OPEN_DISPS(gGlobalCtx->state.gfxCtx);
gSPWideTextureRectangle(
OVERLAY_DISP++,
OTRGetRectDimensionFromRightEdge(left) << 2, top << 2,
(OTRGetRectDimensionFromRightEdge(left) + width) << 2, (top + height) << 2,
G_TX_RENDERTILE,
0, 0,
width_factor, height_factor
);
CLOSE_DISPS(gGlobalCtx->state.gfxCtx);
}

View File

@ -0,0 +1,17 @@
#ifndef GFX_ENHANCEMENT_H
#define GFX_EHHANCEMENT_H
#include "z64.h"
typedef struct {
void *tex;
uint16_t width;
uint16_t height;
uint8_t im_fmt;
uint8_t im_siz;
} sprite_t;
void sprite_load(sprite_t *sprite);
void sprite_draw(sprite_t *sprite, int left, int top, int width, int height);
#endif

View File

@ -0,0 +1,71 @@
#pragma once
enum class Category {
cNull,
cKokiriForest,
cForest,
cGrotto,
cMinigame,
cChestMinigame,
cLostWoods,
cDekuScrub,
cDekuScrubUpgrades,
cNeedSpiritualStones,
cSacredForestMeadow,
cHyruleField,
cLakeHylia,
cGerudo,
cGerudoValley,
cGerudoFortress,
cHauntedWasteland,
cDesertColossus,
cInnerMarket,
cMarket,
cHyruleCastle,
cKakarikoVillage,
cKakariko,
cSkulltulaHouse,
cGraveyard,
cDeathMountainTrail,
cDeathMountain,
cGoronCity,
cDeathMountainCrater,
cZorasRiver,
cZorasDomain,
cZorasFountain,
cLonLonRanch,
cDekuTree,
cDodongosCavern,
cJabuJabusBelly,
cForestTemple,
cFireTemple,
cWaterTemple,
cSpiritTemple,
cShadowTemple,
cBottomOfTheWell,
cIceCavern,
cGerudoTrainingGrounds,
cGanonsCastle,
cSkulltula,
cBossHeart,
cTempleOfTime,
cFairies,
cOutsideGanonsCastle,
cSong,
cSongDungeonReward,
cCow,
cShop,
cMerchant,
cVanillaSmallKey,
cVanillaGFSmallKey,
cVanillaBossKey,
cVanillaMap,
cVanillaCompass,
cAdultTrade,
};
enum class OptionCategory {
Setting,
Cosmetic,
Toggle,
};

View File

@ -0,0 +1,152 @@
#include "cosmetics.hpp"
#include "random.hpp"
#include <sstream>
namespace Cosmetics {
bool ValidHexString(std::string_view hexStr) {
return hexStr.find_first_not_of("0123456789ABCDEFabcdef") == std::string_view::npos && hexStr.length() == 6;
}
Color_RGBAf HexStrToColorRGBAf(const std::string& hexStr) {
uint32_t hex = std::stoi(hexStr, nullptr, 16);
return Color_RGBAf{
.r = ((hex & 0xFF0000) >> 16) / 255.0f,
.g = ((hex & 0x00FF00) >> 8) / 255.0f,
.b = (hex & 0x0000FF) / 255.0f,
.a = 1.0f,
};
}
Color_RGBA8 HexStrToColorRGBA8(const std::string& hexStr) {
uint32_t hex = std::stoi(hexStr, nullptr, 16);
return Color_RGBA8{
.r = (uint8_t)((hex & 0xFF0000) >> 16),
.g = (uint8_t)((hex & 0x00FF00) >> 8),
.b = (uint8_t)(hex & 0x0000FF),
.a = 0xFF,
};
}
std::string CustomColorOptionText(std::string_view color) {
return std::string(CUSTOM_COLOR_STR.substr(0, CUSTOM_COLOR_STR.length() - 6)).append(color);
}
std::string GetCustomColor(const std::string& str) {
return str.substr(str.length() - 6);
}
const std::array<std::string_view, 13> gauntletColors = {
"FFFFFF", //Silver
"FECF0F", //Gold
"000006", //Black
"025918", //Green
"06025A", //Blue
"600602", //Bronze
"FF0000", //Red
"025DB0", //Sky Blue
"FA6A90", //Pink
"FF00FF", //Magenta
"D83A00", //Orange
"5B8A06", //Lime
"800080", //Purple
};
const std::array<std::string_view, 28> tunicColors = {
"168A0E", //Kokiri Green
"96000E", //Goron Red
"002A8E", //Zora Blue
"202020", //Black
"FFFFFF", //White
"FF4A0A", //Orange
"86FF23", //Yellow
"0094B0", //Cyan
"362076", //Indigo
"7B0080", //Purple
"F04F7D", //Pink
"323232", //Dark Gray
"E44B4B", //Salmon
"5A1A20", //Wine Red
"7E705B", //Beige
"583923", //Brown
"72602B", //Sand
"6b7E5D", //Tea Green
"3C5800", //Dark Green
"6CFFF8", //Powder Blue
"005A4B", //Teal
"0259FF", //Sky Blue
"33405E", //Faded Blue
"635AD2", //Lavender
"9C1B4B", //Magenta
"52314F", //Mauve
"505A59", //Silver
"F16F16", //Gold
};
const std::array<std::string_view, 20> naviInnerColors = {
"FFFFFF", //White
"00FF00", //Green
"9696FF", //Light Blue
"FFFF00", //Yellow
"FF0000", //Red
"FF00FF", //Magenta
"FECC3C", //Gold
"000000", //Black
"FFFFFF", //Tatl
"49146C", //Tael
"2C9EC4", //Fi
"E6DE83", //Ciela
"D14902", //Epona
"629C5F", //Ezlo
"A83317", //KoRL
"032660", //Linebeck
"D62E31", //Loftwing
"192426", //Midna
"977A6C", //Phantom Zelda
"FF0000", //Rainbow (starts at red)
};
const std::array<std::string_view, 20> naviOuterColors = {
"0000FF", //White
"00FF00", //Green
"9696FF", //Light Blue
"C89B00", //Yellow
"FF0000", //Red
"C8009B", //Magenta
"FEC007", //Gold
"000000", //Black
"C89800", //Tatl
"FF0000", //Tael
"2C1983", //Fi
"C6BE5B", //Ciela
"551F08", //Epona
"3F5D37", //Ezlo
"DED7C5", //KoRL
"EFFFFF", //Linebeck
"FDE6CC", //Loftwing
"D28330", //Midna
"6F4667", //Phantom Zelda
"FF0000", //Rainbow (starts at red)
};
const std::array<std::string_view, 13> weaponTrailColors = {
"FFFFFF", //White
"000000", //Black
"FF0000", //Red
"00FF00", //Green
"0000FF", //Blue
"FFFF00", //Yellow
"00FFFF", //Cyan
"FF00FF", //Magenta
"FFA500", //Orange
"FFD700", //Gold
"800080", //Purple
"FF69B4", //Pink
"FF0000", //Rainbow (starts at red)
};
//Generate random hex color
std::string RandomColor() {
std::ostringstream color;
color << std::hex << (rand() % 0x1000000); //use default rand to not interfere with main settings
return color.str();
}
} //namespace Cosmetics

View File

@ -0,0 +1,50 @@
#pragma once
#include <array>
#include <string>
#include <vector>
namespace Cosmetics {
constexpr std::string_view RANDOM_CHOICE_STR = "Random Choice";
constexpr std::string_view RANDOM_COLOR_STR = "Random Color";
constexpr std::string_view CUSTOM_COLOR_STR = "Custom #FFFFFF";
constexpr std::string_view CUSTOM_COLOR_PREFIX = "Custom #";
constexpr std::string_view RANDOM_CHOICE_DESC = "A random color from the list of colors will be\nchosen.";
constexpr std::string_view RANDOM_COLOR_DESC = "A completely random color will be chosen.";
constexpr std::string_view CUSTOM_COLOR_DESC = "Press A and type in a custom 6 digit hex color.";
enum CosmeticSettings {
RANDOM_CHOICE,
RANDOM_COLOR,
CUSTOM_COLOR,
NON_COLOR_COUNT,
};
struct Color_RGBAf {
float r;
float g;
float b;
float a;
};
struct Color_RGBA8 {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
extern const std::array<std::string_view, 13> gauntletColors;
extern const std::array<std::string_view, 28> tunicColors;
extern const std::array<std::string_view, 20> naviInnerColors;
extern const std::array<std::string_view, 20> naviOuterColors;
extern const std::array<std::string_view, 13> weaponTrailColors;
bool ValidHexString(std::string_view hexStr);
Color_RGBAf HexStrToColorRGBAf(const std::string& hexStr);
Color_RGBA8 HexStrToColorRGBA8(const std::string& hexStr);
std::string CustomColorOptionText(std::string_view color);
std::string GetCustomColor(const std::string& str);
std::string RandomColor();
} //namespace Cosmetics

View File

@ -0,0 +1,495 @@
#include "custom_messages.hpp"
#include "debug.hpp"
#include "shops.hpp"
#include "z64item.h"
#include <array>
#include <set>
#include <sstream>
namespace CustomMessages {
using namespace std::literals::string_literals;
class MessageEntryComp {
public:
bool operator()(const MessageEntry& lhs, const MessageEntry& rhs) const {
return lhs.id < rhs.id;
}
};
constexpr std::array EnglishDungeonNames = {
"Deku Tree",
"Dodongo's Cavern",
"Jabu Jabu's Belly",
"Forest Temple",
"Fire Temple",
"Water Temple",
"Spirit Temple",
"Shadow Temple",
"Bottom of the Well",
"Ice Cavern",
"Ganon's Tower",
"Gerudo Training Grounds",
"Gerudo Fortress",
"Ganon's Castle",
};
constexpr std::array FrenchDungeonNames = {
"Vénérable Arbre Mojo",
"Caverne Dodongo",
"Ventre de Jabu-Jabu",
"Temple de la Forêt",
"Temple du Feu",
"Temple de l'Eau",
"Temple de l'Esprit",
"Temple de l'Ombre",
"Puits",
"Caverne Polaire",
"Tour de Ganon",
"Gymnase Gerudo",
"Repaire des Voleurs",
"Château de Ganon",
};
constexpr std::array FrenchDungeonArticles = {
"du ",
"de la ",
"du ",
"du ",
"du ",
"du ",
"du ",
"du ",
"du ",
"de la ",
"",
"du ",
"de la ",
"du ",
};
constexpr std::array SpanishDungeonNames = {
"Gran Árbol Deku",
"Cueva de los Dodongos",
"Tripa de Jabu-Jabu",
"Templo del Bosque",
"Templo del Fuego",
"Templo del Agua",
"Templo del Espíritu",
"Templo de las Sombras",
"Fondo del pozo",
"Caverna de hielo",
"Torre de Ganon",
"Centro de Instrucción Gerudo",
"Fortaleza Gerudo",
"Castillo de Ganon",
};
constexpr std::array SpanishDungeonArticles = {
"del",
"de la",
"de la",
"del",
"del",
"del",
"del",
"del",
"del",
"de la",
"de la",
"del",
"de la",
"del",
};
constexpr std::array DungeonColors = {
QM_GREEN,
QM_RED,
QM_BLUE,
QM_GREEN,
QM_RED,
QM_BLUE,
QM_YELLOW,
QM_PINK,
QM_PINK,
QM_LBLUE,
QM_BLACK,
QM_YELLOW,
QM_YELLOW,
QM_RED,
};
std::set<MessageEntry, MessageEntryComp> messageEntries;
std::vector<MessageEntry> arrangedMessageEntries;
std::stringstream messageData;
std::string arrangedMessageData;
//textBoxType and textBoxPosition are defined here: https://wiki.cloudmodding.com/oot/Text_Format#Message_Id
void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition,
std::string englishText, std::string frenchText, std::string spanishText) {
MessageEntry newEntry = { textId, unk_04, textBoxType, textBoxPosition, { 0 } };
while ((englishText.size() % 4) != 0) {
englishText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[ENGLISH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[ENGLISH_U].length = englishText.size();
messageData << englishText;
while ((frenchText.size() % 4) != 0) {
frenchText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[FRENCH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[FRENCH_U].length = frenchText.size();
messageData << frenchText;
while ((spanishText.size() % 4) != 0) {
spanishText += "\0"s;
}
messageData.seekg(0, messageData.end);
newEntry.info[SPANISH_U].offset = (char*)((int)messageData.tellg());
newEntry.info[SPANISH_U].length = spanishText.size();
messageData << spanishText;
messageEntries.insert(newEntry);
}
void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text) {
CreateMessage(textId, unk_04, textBoxType, textBoxPosition, text.GetEnglish(), text.GetFrench(), text.GetSpanish());
}
uint32_t NumMessages() {
return messageEntries.size();
}
std::pair<const char*, uint32_t> RawMessageEntryData() {
arrangedMessageEntries.assign(messageEntries.begin(), messageEntries.end());
const char* data = (const char*)arrangedMessageEntries.data();
uint32_t size = arrangedMessageEntries.size() * sizeof(MessageEntry);
return { data, size };
}
std::pair<const char*, uint32_t> RawMessageData() {
arrangedMessageData = messageData.str();
const char* data = arrangedMessageData.data();
uint32_t size = arrangedMessageData.size();
return { data, size };
}
void CreateAlwaysIncludedMessages() {
// Bombchu (10) Purchase Prompt
CreateMessage(0x8C, 0, 2, 3,
INSTANT_TEXT_ON()+"Bombchu (10): 99 Rupees"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Buy"+NEWLINE()+"Don't buy"+COLOR(QM_WHITE)+MESSAGE_END(),
INSTANT_TEXT_ON()+"Bombchus (10): 99 rubis"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Acheter"+NEWLINE()+"Ne pas acheter"+COLOR(QM_WHITE)+MESSAGE_END(),
INSTANT_TEXT_ON()+"Bombchus (10): 99 rupias"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Comprar"+NEWLINE()+"No comprar"+COLOR(QM_WHITE)+MESSAGE_END());
//Gold Skulltula Tokens (there are two text IDs the game uses)
for (const uint32_t textId : {0xB4, 0xB5}) {
CreateMessage(textId, 0, 2, 3,
INSTANT_TEXT_ON()+"You destroyed a "+COLOR(QM_RED)+"Gold Skulltula"+COLOR(QM_WHITE)+". You got a"+NEWLINE()+"token proving you destroyed it!"+NEWLINE()+NEWLINE()+"You have "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" tokens!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"Vous venez de détruire une "+COLOR(QM_RED)+"Skulltula d'or"+COLOR(QM_WHITE)+"!"+NEWLINE()+"Ce symbole prouve votre prouesse!"+NEWLINE()+NEWLINE()+"Vous avez "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" jetons!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"¡Has eliminado una "+COLOR(QM_RED)+"skulltula dorada"+COLOR(QM_WHITE)+" y has"+NEWLINE()+"conseguido un símbolo para probarlo!"+NEWLINE()+NEWLINE()+"¡Tienes "+COLOR(QM_RED)+SKULLTULAS_DESTROYED()+COLOR(QM_WHITE)+" símbolos!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Bombchu (10) Description
CreateMessage(0xBC, 0, 2, 3,
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchu (10): 99 Rupees"+NEWLINE()+COLOR(QM_WHITE)+"These look like toy mice, but they're"+NEWLINE()+"actually self-propelled time bombs!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchus (10): 99 rubis"+NEWLINE()+COLOR(QM_WHITE)+"Profilée comme une souris mécanique, il"+NEWLINE()+"s'agit en fait d'une bombe à retardement"+NEWLINE()+"autopropulsée!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+"Bombchus (10): 99 rupias"+NEWLINE()+COLOR(QM_WHITE)+"Aunque parezcan ratoncitos de juguete,"+NEWLINE()+"¡son bombas de relojería autopropulsadas!"+INSTANT_TEXT_OFF()+SHOP_MESSAGE_BOX()+MESSAGE_END());
//Boss Keys
for (uint32_t bossKey = 0; bossKey <= (DUNGEON_SHADOW_TEMPLE - DUNGEON_FOREST_TEMPLE); bossKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + bossKey;
CreateMessage(0x9D4 + bossKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Boss Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"Clé d'Or "+NEWLINE()+FrenchDungeonArticles[dungeon]+" "+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[dungeon])+"gran llave "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
CreateMessage(0x9D9, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+EnglishDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+NEWLINE()+"Boss Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+"Clé d'Or "+NEWLINE()+FrenchDungeonArticles[DUNGEON_GANONS_CASTLE_FIRST_PART]+" "+FrenchDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_BOSS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[DUNGEON_GANONS_CASTLE_FIRST_PART])+"gran llave "+SpanishDungeonArticles[DUNGEON_GANONS_CASTLE_FIRST_PART]+NEWLINE()+SpanishDungeonNames[DUNGEON_GANONS_CASTLE_FIRST_PART]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
//Compasses
for (uint32_t dungeon = DUNGEON_DEKU_TREE; dungeon <= DUNGEON_ICE_CAVERN; dungeon++) {
CreateMessage(0x9DA + dungeon, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Compass"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"boussole "+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_COMPASS)+INSTANT_TEXT_ON()+"¡Tienes la "+COLOR(DungeonColors[dungeon])+"brújula "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Maps
for (uint32_t dungeon = DUNGEON_DEKU_TREE; dungeon <= DUNGEON_ICE_CAVERN; dungeon++) {
CreateMessage(0x9E4 + dungeon, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"You got the "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Map"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"Vous trouvez la "+COLOR(DungeonColors[dungeon])+"carte "+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_DUNGEON_MAP)+INSTANT_TEXT_ON()+"¡Has obtenido el "+COLOR(DungeonColors[dungeon])+"mapa "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Small Keys
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_BOTTOM_OF_THE_WELL - DUNGEON_FOREST_TEMPLE); smallKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + smallKey;
CreateMessage(0x9EE + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(DungeonColors[dungeon])+"Petite Clé"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(DungeonColors[dungeon])+"llave pequeña "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_GANONS_CASTLE_FIRST_PART - DUNGEON_GERUDO_TRAINING_GROUNDS); smallKey++) {
uint32_t dungeon = DUNGEON_GERUDO_TRAINING_GROUNDS + smallKey;
CreateMessage(0x9F4 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(DungeonColors[dungeon])+"Petite Clé"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(DungeonColors[dungeon])+"llave pequeña "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Key Rings
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_BOTTOM_OF_THE_WELL - DUNGEON_FOREST_TEMPLE); smallKey++) {
uint32_t dungeon = DUNGEON_FOREST_TEMPLE + smallKey;
CreateMessage(0x9300 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Key Ring"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez un "+COLOR(DungeonColors[dungeon])+"trousseau"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido un "+COLOR(DungeonColors[dungeon])+"llavero "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
for (uint32_t smallKey = 0; smallKey <= (DUNGEON_GANONS_CASTLE_FIRST_PART - DUNGEON_GERUDO_TRAINING_GROUNDS); smallKey++) {
uint32_t dungeon = DUNGEON_GERUDO_TRAINING_GROUNDS + smallKey;
CreateMessage(0x9306 + smallKey, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(DungeonColors[dungeon])+EnglishDungeonNames[dungeon]+NEWLINE()+"Key Ring"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez un "+COLOR(DungeonColors[dungeon])+"trousseau"+NEWLINE()+FrenchDungeonArticles[dungeon]+FrenchDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido un "+COLOR(DungeonColors[dungeon])+"llavero "+SpanishDungeonArticles[dungeon]+NEWLINE()+SpanishDungeonNames[dungeon]+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Tycoon's Wallet
CreateMessage(0x09F7, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"You got a "+COLOR(QM_RED)+"Tycoon's Wallet"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"It's gigantic! Now you can carry"+NEWLINE()+"up to "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"Rupees"+COLOR(QM_WHITE)+"!"+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"Vous obtenez la "+COLOR(QM_RED)+"Bourse de star"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"Elle peut contenir jusqu'à "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"rubis"+COLOR(QM_WHITE)+"!"+NEWLINE()+"C'est gigantesque!"+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_WALLET_GIANT)+INSTANT_TEXT_ON()+"¡Has conseguido una "+COLOR(QM_RED)+"bolsa para ricachones"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+NEWLINE()+"¡Qué descomunal! Ya puedes llevar"+NEWLINE()+"hasta "+COLOR(QM_YELLOW)+"999 "+COLOR(QM_WHITE)+COLOR(QM_YELLOW)+"rupias"+COLOR(QM_WHITE)+"!"+MESSAGE_END());
//Saria's Song Default Hint
CreateMessage(0x0A00, 0, 2, 3,
UNSKIPPABLE()+"Have you tried talking to the gossip"+NEWLINE()+ "stones around Hyrule? They might have"+NEWLINE()+"some good advice... Hee hee!"+WAIT_FOR_INPUT()+"If you learn something from the gossip stones,"+NEWLINE()+"I will remember it!"+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"As-tu parlé aux pierres à potins"+NEWLINE()+ "dans Hyrule? Elles sont de bons conseils..."+NEWLINE()+"Hi hi!"+WAIT_FOR_INPUT()+"Si elles te révèlent quelque chose,"+NEWLINE()+"je m'en souviendrai!"+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"¿Has probado a consultarle a las"+NEWLINE()+ "piedras chismosas esparcidas por Hyrule? Puede"+NEWLINE()+"que sean de ayuda a tu empresa... ¡Ji, ji!"+WAIT_FOR_INPUT()+"¡Puedo recordarte todo lo que aprendas de ellas,"+NEWLINE()+"si así lo deseas!"+EVENT_TRIGGER()+MESSAGE_END());
//Poe Collector (when enough has been sold)
CreateMessage(0x70F8, 0, 0, 0,
UNSKIPPABLE()+"Wait a minute! WOW!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"You have earned enough points!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Young man, you are a genuine "+COLOR(QM_RED)+"ghost hunter"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Is that what you expected me to say?"+NEWLINE()+"Heh heh heh!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Because of you, I have extra inventory of"+NEWLINE()+COLOR(QM_RED)+"Big Poes"+COLOR(QM_WHITE)+", so this will be the last time I can"+NEWLINE()
+"buy one of these ghosts."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"You're thinking about what I promised would"+NEWLINE()+"happen when you earned enough points."+NEWLINE()+"Heh heh."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Don't worry. I didn't forget. Just take this."+MESSAGE_END(),
UNSKIPPABLE()+"Ooooh! WHOA!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Tu as obtenu suffisamment de points!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Tu es un véritable "+COLOR(QM_RED)+"chasseur de fantômes"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Il est content, hein?"+NEWLINE()+"Il est content le monsieur?"+NEWLINE()+"Hé hé hé!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Grâce à toi, mon stock d'"+COLOR(QM_RED)+"Âmes"+COLOR(QM_WHITE)+" est plein!"+NEWLINE()+"C'est donc la dernière fois que nous"+NEWLINE()+"faisons affaire."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Je sais, je sais..."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Nous avions passé un pacte..."+NEWLINE()+"Tu as eu tes points et je t'en félicite..."+NEWLINE()+"Hé hé hé!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Alors prends donc ceci, mon bon ami!"+MESSAGE_END(),
UNSKIPPABLE()+"¡Un momento! ¡OYE!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Has conseguido los puntos suficientes!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Jovencito, eres un auténtico "+COLOR(QM_RED)+"cazador de"+NEWLINE()+"fantasmas"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"¿Era eso lo que esperabas que dijera?"+NEWLINE()+"¡Je, je je!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Gracias a ti, ya tengo la cantidad necesaria"+NEWLINE()+"de "+COLOR(QM_RED)+"grandes poes"+COLOR(QM_WHITE)+", así que esta será la"+NEWLINE()
+"última vez que te compre unos de ese tipo."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¿Recuerdas lo que te dije que ocurriría"+NEWLINE()+"cuando tuvieses suficientes puntos?"+NEWLINE()+"Je, je, je."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Tranquilo, que no se me ha olvidado."+NEWLINE()+"Toma esto."+MESSAGE_END());
//Ice Trap
CreateMessage(0x9002, 0, 2, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"FOOL!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"IDIOT!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+COLOR(QM_RED)+"¡TONTO!"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
//Business Scrubs
//The less significant byte represents the price of the item
for (uint32_t price = 0; price <= 95; price += 5) {
CreateMessage(0x9000 + price, 0, 0, 0,
INSTANT_TEXT_ON()+"I'll sell you something good for "+COLOR(QM_RED)+std::to_string(price)+" Rupees"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"OK"+NEWLINE()+"No way"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"Je te vends un truc super pour "+COLOR(QM_RED)+std::to_string(price)+" Rubis"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"D'accord"+NEWLINE()+"Hors de question"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+"¡Te puedo vender algo bueno por "+COLOR(QM_RED)+std::to_string(price)+" rupias"+COLOR(QM_WHITE)+"!"+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Vale"+NEWLINE()+"Ni hablar"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//Poe Collector
//The last digit represent the number of poes needed to collect
for (uint32_t poes = 1; poes <= 10; poes++) {
CreateMessage(0x9080 + poes, 0, 0, 0,
UNSKIPPABLE()+"Oh, you brought a Poe today!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Hmmmm!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Very interesting! This is a "+COLOR(QM_RED)+"Big Poe"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"I'll buy it for "+COLOR(QM_RED)+"50 Rupees"+COLOR(QM_WHITE)+"."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"On top of that, I'll put "+COLOR(QM_RED)+"100 points "+COLOR(QM_WHITE)+"on"+NEWLINE()+"your card."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"If you earn "+COLOR(QM_RED)+std::to_string(poes * 100)+" points"+COLOR(QM_WHITE)+", you'll be a"+NEWLINE()+"happy man! Heh heh."+MESSAGE_END(),
UNSKIPPABLE()+"Oh! Tu as apporté un fantôme!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Hmmmm!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Magnifique!"+NEWLINE()+"C'est une "+COLOR(QM_RED)+"Âme"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Je t'en donne "+COLOR(QM_RED)+"50 Rubis"+COLOR(QM_WHITE)+"."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Et en plus, j'inscris "+COLOR(QM_RED)+"100 points "+COLOR(QM_WHITE)+NEWLINE()+"sur ta carte."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Obtiens "+COLOR(QM_RED)+std::to_string(poes * 100)+" points"+COLOR(QM_WHITE)+" et tu ne"+NEWLINE()+"seras pas déçu..."+NEWLINE()+"Hé hé hé."+MESSAGE_END(),
UNSKIPPABLE()+"¡Vaya! ¡Traes un poe!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Mmm! ¿A ver?"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Qué interesante! ¡Es un "+COLOR(QM_RED)+"gran poe"+COLOR(QM_WHITE)+"!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"Te daré "+COLOR(QM_RED)+"50 rupias "+COLOR(QM_WHITE)+"por él."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Y además agregaré "+COLOR(QM_RED)+"100 puntos "+COLOR(QM_WHITE)+"a tu"+NEWLINE()+"tarjeta."+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()
+"¡Si llegas a "+COLOR(QM_RED)+std::to_string(poes * 100)+" puntos"+COLOR(QM_WHITE)+", serás muy feliz!"+NEWLINE()+"Je, je, je..."+MESSAGE_END());
}
//Talon (this is to prevent accidentally skipping Malon in HC)
CreateMessage(0x9100, 0, 2, 0,
UNSKIPPABLE()+"You should go talk to my daughter Malon,"+NEWLINE()+"she has an item for you."+NEWLINE()+SET_SPEED(3)+"........."+SET_SPEED(0)+WAIT_FOR_INPUT()+"I have to think about some stuff now,"+NEWLINE()+"please don't distract me."+MESSAGE_END(),
UNSKIPPABLE()+"Zzzz... Muh... Malon..."+NEWLINE()+"Parler avec... Malon..."+NEWLINE()+SET_SPEED(3)+"........."+SET_SPEED(0)+WAIT_FOR_INPUT()+"Si fatigué..."+NEWLINE()+"Quelle vie..."+MESSAGE_END(),
UNSKIPPABLE()+"Habla con Malon, tiene algo que darte..."+SET_SPEED(3)+"........."+SET_SPEED(0)+MESSAGE_END());
//Bow Shooting Gallery reminder
CreateMessage(0x9140, 0, 0, 0,
UNSKIPPABLE()+"Wonderful! Bravo! Perfect!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Here's a fantastic present! But I have"+COLOR(QM_RED)+NEWLINE()+"something else "+COLOR(QM_WHITE)+"for you once you have a bow."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"Merveilleux! Bravo! C'est parfait!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"Voici un prix fantastique! J'aurai "+COLOR(QM_RED)+"autre chose"+COLOR(QM_WHITE)+NEWLINE()+"pour toi quand tu auras un arc."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END(),
UNSKIPPABLE()+"¡Espectacular! ¡Bravo! ¡Perfecto!"+WAIT_FOR_INPUT()+NEWLINE()+UNSKIPPABLE()+"¡Toma este sensacional regalo! Pero te tengo"+NEWLINE()+"guardado "+COLOR(QM_RED)+"algo más "+COLOR(QM_WHITE)+"para cuando traigas tu"+NEWLINE()+"propio arco."+SET_SPEED(30)+" "+EVENT_TRIGGER()+MESSAGE_END());
//Shopsanity items
//64 textboxes, 2 for each of 32 potential shopsanity items
for(uint32_t shopitems = 0; shopitems < NonShopItems.size(); shopitems++) {
Text name = NonShopItems[shopitems].Name;
std::string price = std::to_string(NonShopItems[shopitems].Price);
//Prevent names from being too long and overflowing textbox
if (name.GetEnglish() == "Piece of Heart (Treasure Chest Minigame)") {
name = Text{"Piece of Heart", "Quart de Coeur", "Pieza de corazón"};
} else if (name.GetEnglish() == "Green Rupee (Treasure Chest Minigame)") {
name = Text{"Green Rupee", "Rubis Vert", "Rupia verde"};
}
//Message to display when hovering over the item
if (NonShopItems[shopitems].Repurchaseable) { //Different checkbox for repurchaseable items
CreateMessage(0x9200+shopitems*2, 0, 0, 0,
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetEnglish()+": "+price+" Rupees"+NEWLINE()+COLOR(QM_WHITE)+"Special deal!"+NEWLINE()+"Buy as many as you want!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetFrench()+": "+price+" rubis"+NEWLINE()+COLOR(QM_WHITE)+"Offre spéciale!"+NEWLINE()+"Achetez-en à volonté!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetSpanish()+": "+price+" rupias"+NEWLINE()+COLOR(QM_WHITE)+"¡Oferta especial!"+NEWLINE()+"¡Compra todo lo que quieras!"+SHOP_MESSAGE_BOX()+MESSAGE_END());
}
else { //Normal textbox
CreateMessage(0x9200+shopitems*2, 0, 0, 0,
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetEnglish()+": "+price+" Rupees"+NEWLINE()+COLOR(QM_WHITE)+"Special deal! ONE LEFT!"+NEWLINE()+"Get it while it lasts!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetFrench()+": "+price+" rubis"+NEWLINE()+COLOR(QM_WHITE)+"Offre spéciale! DERNIER EN STOCK!"+NEWLINE()+"Faites vite!"+SHOP_MESSAGE_BOX()+MESSAGE_END(),
INSTANT_TEXT_ON()+COLOR(QM_RED)+name.GetSpanish()+": "+price+" rupias"+NEWLINE()+COLOR(QM_WHITE)+"¡Oferta especial! ¡SOLO QUEDA UNA UNIDAD!"+NEWLINE()+"¡Hazte con ella antes de que se agote!"+SHOP_MESSAGE_BOX()+MESSAGE_END());
}
//Message to display when going to buy the item
CreateMessage(0x9200+shopitems*2+1, 0, 0, 0,
INSTANT_TEXT_ON()+name.GetEnglish()+": "+price+" Rupees"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Buy"+NEWLINE()+"Don't buy"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+name.GetFrench()+": "+price+" rubis"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Acheter"+NEWLINE()+"Ne pas acheter"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END(),
INSTANT_TEXT_ON()+name.GetSpanish()+": "+price+" rupias"+INSTANT_TEXT_OFF()+NEWLINE()+NEWLINE()+TWO_WAY_CHOICE()+COLOR(QM_GREEN)+"Comprar"+NEWLINE()+"No comprar"+COLOR(QM_WHITE)+INSTANT_TEXT_OFF()+MESSAGE_END());
}
//easter egg
CreateMessage(0x96F, 0, 2, 2,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Oh hey, you watched all the credits!"+NEWLINE()+CENTER_TEXT()+"Here's a prize for your patience."+NEWLINE()+CENTER_TEXT()+"Unlocking MQ and saving..."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"Do not remove the Game Card"+NEWLINE()+CENTER_TEXT()+"or turn the power off."+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"The Legend of Zelda Ocarina of Time 3D"+NEWLINE()+CENTER_TEXT()+"Master Quest va être déverrouillé."+NEWLINE()+CENTER_TEXT()+"Sauvegarde... Veuillez patienter."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"N'éteignez pas la console et"+NEWLINE()+CENTER_TEXT()+"ne retirez pas la carte de jeu"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Desbloqueando The Legend of Zelda"+NEWLINE()+CENTER_TEXT()+"Ocarina of Time 3D Master Quest."+NEWLINE()+CENTER_TEXT()+"Guardando. Espera un momento..."+NEWLINE()+NEWLINE()+CENTER_TEXT()+COLOR(QM_RED)+"No saques la tarjeta de juego"+NEWLINE()+CENTER_TEXT()+"ni apagues la consola."+INSTANT_TEXT_OFF()+MESSAGE_END());
CreateMessage(0x970, 0, 2, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Master Quest doesn't affect the Randomizer,"+NEWLINE()+CENTER_TEXT()+"so you can use 3 more save slots now."+NEWLINE()+NEWLINE()+CENTER_TEXT()+"Thanks for playing!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Vous pouvez désormais jouer à"+NEWLINE()+CENTER_TEXT()+"The Legend of Zelda Ocarina of Time 3D"+NEWLINE()+CENTER_TEXT()+"Master Quest!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"¡Ya puedes jugar The Legend of Zelda"+NEWLINE()+CENTER_TEXT()+"Ocarina of Time 3D Master Quest!"+INSTANT_TEXT_OFF()+MESSAGE_END());
//Messages for the new Lake Hylia switch
CreateMessage(0x346, 0, 1, 3,
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Water level control system."+NEWLINE()+CENTER_TEXT()+"Keep away!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Contrôle du niveau d'eau."+NEWLINE()+CENTER_TEXT()+"Ne pas toucher!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+INSTANT_TEXT_ON()+CENTER_TEXT()+"Control del nivel del agua."+NEWLINE()+CENTER_TEXT()+"¡No te acerques!"+INSTANT_TEXT_OFF()+MESSAGE_END());
CreateMessage(0x1B3, 0, 0, 3,
UNSKIPPABLE()+"This switch is rustier than you think."+WAIT_FOR_INPUT()+"Something must be wrong with the pipe"+NEWLINE()+"system in the Water Temple."+MESSAGE_END(),
UNSKIPPABLE()+"Cet interrupteur est très rouillé."+WAIT_FOR_INPUT()+"Quelque chose ne va pas avec"+NEWLINE()+"la tuyauterie du Temple de l'Eau."+MESSAGE_END(),
UNSKIPPABLE()+"El interruptor está más oxidado de lo que"+NEWLINE()+"aparenta."+WAIT_FOR_INPUT()+"Algo debe andar mal en el sistema de"+NEWLINE()+"cañerías del Templo del Agua."+MESSAGE_END());
//Treasure chest shop keys. If they're not randomized leave the base game text
if (Settings::ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_SINGLE_KEYS)) {
CreateMessage(0x0F3, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got a "+COLOR(QM_RED)+"Treasure Chest Shop"+NEWLINE()+"Small Key"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez une "+COLOR(QM_RED)+"Petite Clé"+NEWLINE()+"de la Chasse-aux-Trésors"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido una "+COLOR(QM_RED)+"llave pequeña del"+NEWLINE()+"Cofre del Tesoro"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
} else if (Settings::ShuffleChestMinigame.Is(SHUFFLECHESTMINIGAME_PACK)) {
CreateMessage(0x0F3, 0, 2, 3,
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"You got all 6 "+COLOR(QM_RED)+"Treasure Chest Shop"+NEWLINE()+"Small Keys"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"Vous trouvez les "+COLOR(QM_RED)+"petites clés"+NEWLINE()+"de la Chasse-aux-Trésors"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END(),
UNSKIPPABLE()+ITEM_OBTAINED(ITEM_KEY_SMALL)+INSTANT_TEXT_ON()+"¡Has obtenido todas las 6 "+COLOR(QM_RED)+"llaves"+NEWLINE()+"pequeñas del Cofre del Tesoro"+COLOR(QM_WHITE)+"!"+INSTANT_TEXT_OFF()+MESSAGE_END());
}
}
Text AddColorsAndFormat(Text text, const std::vector<uint8_t>& colors /*= {}*/) {
//for each language
for (std::string* textStr : {&text.english, &text.french, &text.spanish}) {
//insert playername
size_t atSymbol = textStr->find('@');
while (atSymbol != std::string::npos) {
textStr->replace(atSymbol, 1, PLAYER_NAME());
atSymbol = textStr->find('@');
}
//insert newlines either manually or when encountering a '&'
constexpr size_t lineLength = 44;
size_t lastNewline = 0;
while (lastNewline + lineLength < textStr->length()) {
size_t carrot = textStr->find('^', lastNewline);
size_t ampersand = textStr->find('&', lastNewline);
size_t lastSpace = textStr->rfind(' ', lastNewline + lineLength);
size_t lastPeriod = textStr->rfind('.', lastNewline + lineLength);
//replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
textStr->replace(ampersand, 1, NEWLINE());
lastNewline = ampersand + NEWLINE().length();
//or move the lastNewline cursor to the next line if a '^' is encountered
} else if (carrot < lastNewline + lineLength) {
lastNewline = carrot + 1;
//some lines need to be split but don't have spaces, look for periods instead
} else if (lastSpace == std::string::npos) {
textStr->replace(lastPeriod, 1, "."+NEWLINE());
lastNewline = lastPeriod + NEWLINE().length() + 1;
} else {
textStr->replace(lastSpace, 1, NEWLINE());
lastNewline = lastSpace + NEWLINE().length();
}
}
//clean up any remaining '&' characters
size_t ampersand = textStr->find('&');
while (ampersand != std::string::npos) {
textStr->replace(ampersand, 1, NEWLINE());
ampersand = textStr->find('&');
}
//insert box break
size_t carrotSymbol = textStr->find('^');
while (carrotSymbol != std::string::npos) {
textStr->replace(carrotSymbol, 1, INSTANT_TEXT_OFF()+WAIT_FOR_INPUT()+INSTANT_TEXT_ON());
carrotSymbol = textStr->find('^');
}
//add colors
for (auto color : colors) {
size_t firstHashtag = textStr->find('#');
if (firstHashtag != std::string::npos) {
textStr->replace(firstHashtag, 1, COLOR(color));
size_t secondHashtag = textStr->find('#');
if (secondHashtag == std::string::npos) {
CitraPrint("ERROR: Couldn't find second '#' in " + (*textStr));
} else {
textStr->replace(secondHashtag, 1, COLOR(QM_WHITE));
}
}
}
}
return Text{"","",""}+UNSKIPPABLE()+INSTANT_TEXT_ON()+text+INSTANT_TEXT_OFF()+MESSAGE_END();
}
void ClearMessages() {
messageEntries.clear();
arrangedMessageEntries.clear();
messageData.str("");
arrangedMessageData = "";
}
std::string MESSAGE_END() { return "\x7F\x00"s; }
std::string WAIT_FOR_INPUT() { return "\x7F\x01"s; }
std::string HORIZONTAL_SPACE(uint8_t x) {
return "\x7F\x02"s + char(x);
}
std::string GO_TO(uint16_t x) {
return "\x7F\x03"s + char(x >> 8) + char(x & 0x00FF);
}
std::string INSTANT_TEXT_ON() { return "\x7F\x04"s; }
std::string INSTANT_TEXT_OFF() { return "\x7F\x05"s; }
std::string SHOP_MESSAGE_BOX() { return "\x7F\x06\x00"s; }
std::string EVENT_TRIGGER() { return "\x7F\x07"s; }
std::string DELAY_FRAMES(uint8_t x) {
return "\x7F\x08"s + char(x);
}
std::string CLOSE_AFTER(uint8_t x) {
return "\x7F\x0A"s + char(x);
}
std::string PLAYER_NAME() { return "\x7F\x0B"s; }
std::string PLAY_OCARINA() { return "\x7F\x0C"s; }
std::string ITEM_OBTAINED(uint8_t x) {
return "\x7F\x0F"s + char(x);
}
std::string SET_SPEED(uint8_t x) {
return "\x7F\x10"s + char(x);
}
std::string SKULLTULAS_DESTROYED() { return "\x7F\x15"s; }
std::string CURRENT_TIME() { return "\x7F\x17"s; }
std::string UNSKIPPABLE() { return "\x7F\x19"s; }
std::string TWO_WAY_CHOICE() { return "\x7F\x1A\xFF\xFF\xFF\xFF"s; }
std::string NEWLINE() { return "\x7F\x1C"s; }
std::string COLOR(uint8_t x) { return "\x7F\x1D"s + char(x); }
std::string CENTER_TEXT() { return "\x7F\x1E"s; }
std::string IF_NOT_MQ() { return "\x7F\x29"s; }
std::string MQ_ELSE() { return "\x7F\x2A"s; }
std::string MQ_END() { return "\x7F\x2B"s; }
}

View File

@ -0,0 +1,93 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "text.hpp"
#define QM_WHITE 0x00
#define QM_RED 0x41
#define QM_GREEN 0x42
#define QM_BLUE 0x43
#define QM_LBLUE 0x44
#define QM_PINK 0x45
#define QM_YELLOW 0x46
#define QM_BLACK 0x47
namespace CustomMessages {
typedef struct {
// In the true file format, offset is the offset into the QM file.
// In randomizer, offset will be a pointer to the text in the game's address space.
// Since these pointers will be much larger as u32 than the original script's offsets,
// We will be able to distinguish between original and custom text using their numerical value.
const char* offset;
uint32_t length;
} MessageLanguageInfo;
typedef enum {
/* 0x00 */ JAPANESE_J,
/* 0x01 */ ENGLISH_U,
/* 0x02 */ ENGLISH_E,
/* 0x03 */ GERMAN_E,
/* 0x04 */ FRENCH_E,
/* 0x05 */ FRENCH_U,
/* 0x06 */ SPANISH_E,
/* 0x07 */ SPANISH_U,
/* 0x08 */ ITALIAN_E,
/* 0x09 */ DUTCH_E,
} MessageLanguage;
typedef struct {
uint32_t id;
uint32_t unk_04;
uint32_t unk_08;
uint32_t unk_0C;
MessageLanguageInfo info[10];
} MessageEntry; // size = 0x60
typedef struct {
char magic[4]; //"QM\0\0"
uint32_t unk_04;
uint32_t numEntries;
uint32_t unk_0C;
} MessageFileHeader;
void CreateMessage(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition,
std::string englishText, std::string frenchText, std::string spanishText);
void CreateMessageFromTextObject(uint32_t textId, uint32_t unk_04, uint32_t textBoxType, uint32_t textBoxPosition, const Text& text);
uint32_t NumMessages();
std::pair<const char*, uint32_t> RawMessageEntryData();
std::pair<const char*, uint32_t> RawMessageData();
void CreateAlwaysIncludedMessages();
Text AddColorsAndFormat(Text text, const std::vector<uint8_t>& colors = {});
void ClearMessages();
std::string MESSAGE_END();
std::string WAIT_FOR_INPUT();
std::string HORIZONTAL_SPACE(uint8_t x);
std::string GO_TO(uint16_t x);
std::string INSTANT_TEXT_ON();
std::string INSTANT_TEXT_OFF();
std::string SHOP_MESSAGE_BOX();
std::string EVENT_TRIGGER();
std::string DELAY_FRAMES(uint8_t x);
std::string CLOSE_AFTER(uint8_t x);
std::string PLAYER_NAME();
std::string PLAY_OCARINA();
std::string ITEM_OBTAINED(uint8_t x);
std::string SET_SPEED(uint8_t x);
std::string SKULLTULAS_DESTROYED();
std::string CURRENT_TIME();
std::string UNSKIPPABLE();
std::string TWO_WAY_CHOICE();
std::string NEWLINE();
std::string COLOR(uint8_t x);
std::string CENTER_TEXT();
std::string IF_NOT_MQ();
std::string MQ_ELSE();
std::string MQ_END();
}

View File

@ -0,0 +1,5 @@
#include "debug.hpp"
void CitraPrint (std::string_view str) {
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <string_view>
void CitraPrint(std::string_view str);

View File

@ -0,0 +1,616 @@
#include "dungeon.hpp"
#include "category.hpp"
#include "item_location.hpp"
#include "pool_functions.hpp"
#include "keys.hpp"
namespace Dungeon {
DungeonInfo::DungeonInfo(std::string name_, uint32_t map_, uint32_t compass_, uint32_t smallKey_, uint32_t keyRing_,
uint32_t bossKey_, uint8_t vanillaKeyCount_, uint8_t mqKeyCount_,
std::vector<uint32_t> vanillaLocations_,
std::vector<uint32_t> mqLocations_,
std::vector<uint32_t> sharedLocations_)
: name(std::move(name_)),
map(map_),
compass(compass_),
smallKey(smallKey_),
keyRing(keyRing_),
bossKey(bossKey_),
vanillaKeyCount(vanillaKeyCount_),
mqKeyCount(mqKeyCount_),
vanillaLocations(std::move(vanillaLocations_)),
mqLocations(std::move(mqLocations_)),
sharedLocations(std::move(sharedLocations_)) {}
DungeonInfo::~DungeonInfo() = default;
uint32_t DungeonInfo::GetSmallKey() const {
return smallKey;
}
uint32_t DungeonInfo::GetKeyRing() const {
return keyRing;
}
uint32_t DungeonInfo::GetMap() const {
return map;
}
uint32_t DungeonInfo::GetCompass() const {
return compass;
}
uint32_t DungeonInfo::GetBossKey() const {
return bossKey;
}
void DungeonInfo::PlaceVanillaMap() {
if (map == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto mapLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaMap); })[0];
PlaceItemInLocation(mapLocation, map);
}
void DungeonInfo::PlaceVanillaCompass() {
if (compass == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto compassLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaCompass); })[0];
PlaceItemInLocation(compassLocation, compass);
}
void DungeonInfo::PlaceVanillaBossKey() {
if (bossKey == NONE || bossKey == GANONS_CASTLE_BOSS_KEY) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto bossKeyLocation = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaBossKey); })[0];
PlaceItemInLocation(bossKeyLocation, bossKey);
}
void DungeonInfo::PlaceVanillaSmallKeys() {
if (smallKey == NONE) {
return;
}
auto dungeonLocations = GetDungeonLocations();
auto smallKeyLocations = FilterFromPool(dungeonLocations, [](const uint32_t loc){ return Location(loc)->IsCategory(Category::cVanillaSmallKey); });
for (auto location : smallKeyLocations) {
PlaceItemInLocation(location, smallKey);
}
}
//Gets the chosen dungeon locations for a playthrough (so either MQ or Vanilla)
std::vector<uint32_t> DungeonInfo::GetDungeonLocations() const {
auto locations = masterQuest ? mqLocations : vanillaLocations;
AddElementsToPool(locations, sharedLocations);
return locations;
}
//Gets all dungeon locations (MQ + Vanilla)
std::vector<uint32_t> DungeonInfo::GetEveryLocation() const {
auto locations = vanillaLocations;
AddElementsToPool(locations, mqLocations);
AddElementsToPool(locations, sharedLocations);
return locations;
}
DungeonInfo DekuTree = DungeonInfo("Deku Tree", DEKU_TREE_MAP, DEKU_TREE_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
DEKU_TREE_MAP_CHEST,
DEKU_TREE_COMPASS_CHEST,
DEKU_TREE_COMPASS_ROOM_SIDE_CHEST,
DEKU_TREE_BASEMENT_CHEST,
DEKU_TREE_SLINGSHOT_CHEST,
DEKU_TREE_SLINGSHOT_ROOM_SIDE_CHEST,
DEKU_TREE_GS_BASEMENT_BACK_ROOM,
DEKU_TREE_GS_BASEMENT_GATE,
DEKU_TREE_GS_BASEMENT_VINES,
DEKU_TREE_GS_COMPASS_ROOM,
}, {
//MQ Locations
DEKU_TREE_MQ_MAP_CHEST,
DEKU_TREE_MQ_COMPASS_CHEST,
DEKU_TREE_MQ_SLINGSHOT_CHEST,
DEKU_TREE_MQ_SLINGSHOT_ROOM_BACK_CHEST,
DEKU_TREE_MQ_BASEMENT_CHEST,
DEKU_TREE_MQ_BEFORE_SPINNING_LOG_CHEST,
DEKU_TREE_MQ_AFTER_SPINNING_LOG_CHEST,
DEKU_TREE_MQ_DEKU_SCRUB,
DEKU_TREE_MQ_GS_LOBBY,
DEKU_TREE_MQ_GS_COMPASS_ROOM,
DEKU_TREE_MQ_GS_BASEMENT_GRAVES_ROOM,
DEKU_TREE_MQ_GS_BASEMENT_BACK_ROOM,
}, {
//Shared Locations
DEKU_TREE_QUEEN_GOHMA_HEART,
QUEEN_GOHMA,
});
DungeonInfo DodongosCavern = DungeonInfo("Dodongo's Cavern", DODONGOS_CAVERN_MAP, DODONGOS_CAVERN_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
DODONGOS_CAVERN_MAP_CHEST,
DODONGOS_CAVERN_COMPASS_CHEST,
DODONGOS_CAVERN_BOMB_FLOWER_PLATFORM_CHEST,
DODONGOS_CAVERN_BOMB_BAG_CHEST,
DODONGOS_CAVERN_END_OF_BRIDGE_CHEST,
DODONGOS_CAVERN_DEKU_SCRUB_LOBBY,
DODONGOS_CAVERN_DEKU_SCRUB_SIDE_ROOM_NEAR_DODONGOS,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_LEFT,
DODONGOS_CAVERN_DEKU_SCRUB_NEAR_BOMB_BAG_RIGHT,
DODONGOS_CAVERN_GS_VINES_ABOVE_STAIRS,
DODONGOS_CAVERN_GS_SCARECROW,
DODONGOS_CAVERN_GS_ALCOVE_ABOVE_STAIRS,
DODONGOS_CAVERN_GS_BACK_ROOM,
DODONGOS_CAVERN_GS_SIDE_ROOM_NEAR_LOWER_LIZALFOS,
}, {
//MQ Locations
DODONGOS_CAVERN_MQ_MAP_CHEST,
DODONGOS_CAVERN_MQ_BOMB_BAG_CHEST,
DODONGOS_CAVERN_MQ_COMPASS_CHEST,
DODONGOS_CAVERN_MQ_LARVAE_ROOM_CHEST,
DODONGOS_CAVERN_MQ_TORCH_PUZZLE_ROOM_CHEST,
DODONGOS_CAVERN_MQ_UNDER_GRAVE_CHEST,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_REAR,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_FRONT,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_STAIRCASE,
DODONGOS_CAVERN_MQ_DEKU_SCRUB_SIDE_ROOM_NEAR_LOWER_LIZALFOS,
DODONGOS_CAVERN_MQ_GS_SCRUB_ROOM,
DODONGOS_CAVERN_MQ_GS_SONG_OF_TIME_BLOCK_ROOM,
DODONGOS_CAVERN_MQ_GS_LIZALFOS_ROOM,
DODONGOS_CAVERN_MQ_GS_LARVAE_ROOM,
DODONGOS_CAVERN_MQ_GS_BACK_AREA,
}, {
//Shared Locations
DODONGOS_CAVERN_BOSS_ROOM_CHEST,
DODONGOS_CAVERN_KING_DODONGO_HEART,
KING_DODONGO,
});
DungeonInfo JabuJabusBelly = DungeonInfo("Jabu Jabu's Belly", JABU_JABUS_BELLY_MAP, JABU_JABUS_BELLY_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
JABU_JABUS_BELLY_MAP_CHEST,
JABU_JABUS_BELLY_COMPASS_CHEST,
JABU_JABUS_BELLY_BOOMERANG_CHEST,
JABU_JABUS_BELLY_DEKU_SCRUB,
JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_LOWER,
JABU_JABUS_BELLY_GS_LOBBY_BASEMENT_UPPER,
JABU_JABUS_BELLY_GS_NEAR_BOSS,
JABU_JABUS_BELLY_GS_WATER_SWITCH_ROOM,
}, {
//MQ Locations
JABU_JABUS_BELLY_MQ_FIRST_ROOM_SIDE_CHEST,
JABU_JABUS_BELLY_MQ_MAP_CHEST,
JABU_JABUS_BELLY_MQ_SECOND_ROOM_LOWER_CHEST,
JABU_JABUS_BELLY_MQ_COMPASS_CHEST,
JABU_JABUS_BELLY_MQ_SECOND_ROOM_UPPER_CHEST,
JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_SWITCHES_CHEST,
JABU_JABUS_BELLY_MQ_BASEMENT_NEAR_VINES_CHEST,
JABU_JABUS_BELLY_MQ_NEAR_BOSS_CHEST,
JABU_JABUS_BELLY_MQ_FALLING_LIKE_LIKE_ROOM_CHEST,
JABU_JABUS_BELLY_MQ_BOOMERANG_ROOM_SMALL_CHEST,
JABU_JABUS_BELLY_MQ_BOOMERANG_CHEST,
JABU_JABUS_BELLY_MQ_COW,
JABU_JABUS_BELLY_MQ_GS_TAILPASARAN_ROOM,
JABU_JABUS_BELLY_MQ_GS_INVISIBLE_ENEMIES_ROOM,
JABU_JABUS_BELLY_MQ_GS_BOOMERANG_CHEST_ROOM,
JABU_JABUS_BELLY_MQ_GS_NEAR_BOSS,
}, {
//Shared Locations
JABU_JABUS_BELLY_BARINADE_HEART,
BARINADE,
});
DungeonInfo ForestTemple = DungeonInfo("Forest Temple", FOREST_TEMPLE_MAP, FOREST_TEMPLE_COMPASS, FOREST_TEMPLE_SMALL_KEY, FOREST_TEMPLE_KEY_RING, FOREST_TEMPLE_BOSS_KEY, 5, 6, {
//Vanilla Locations
FOREST_TEMPLE_FIRST_ROOM_CHEST,
FOREST_TEMPLE_FIRST_STALFOS_CHEST,
FOREST_TEMPLE_RAISED_ISLAND_COURTYARD_CHEST,
FOREST_TEMPLE_MAP_CHEST,
FOREST_TEMPLE_WELL_CHEST,
FOREST_TEMPLE_FALLING_CEILING_ROOM_CHEST,
FOREST_TEMPLE_EYE_SWITCH_CHEST,
FOREST_TEMPLE_BOSS_KEY_CHEST,
FOREST_TEMPLE_FLOORMASTER_CHEST,
FOREST_TEMPLE_BOW_CHEST,
FOREST_TEMPLE_RED_POE_CHEST,
FOREST_TEMPLE_BLUE_POE_CHEST,
FOREST_TEMPLE_BASEMENT_CHEST,
FOREST_TEMPLE_GS_RAISED_ISLAND_COURTYARD,
FOREST_TEMPLE_GS_FIRST_ROOM,
FOREST_TEMPLE_GS_LEVEL_ISLAND_COURTYARD,
FOREST_TEMPLE_GS_LOBBY,
FOREST_TEMPLE_GS_BASEMENT,
}, {
//MQ Locations
FOREST_TEMPLE_MQ_FIRST_ROOM_CHEST,
FOREST_TEMPLE_MQ_WOLFOS_CHEST,
FOREST_TEMPLE_MQ_BOW_CHEST,
FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_LOWER_CHEST,
FOREST_TEMPLE_MQ_RAISED_ISLAND_COURTYARD_UPPER_CHEST,
FOREST_TEMPLE_MQ_WELL_CHEST,
FOREST_TEMPLE_MQ_MAP_CHEST,
FOREST_TEMPLE_MQ_COMPASS_CHEST,
FOREST_TEMPLE_MQ_FALLING_CEILING_ROOM_CHEST,
FOREST_TEMPLE_MQ_BASEMENT_CHEST,
FOREST_TEMPLE_MQ_REDEAD_CHEST,
FOREST_TEMPLE_MQ_BOSS_KEY_CHEST,
FOREST_TEMPLE_MQ_GS_FIRST_HALLWAY,
FOREST_TEMPLE_MQ_GS_BLOCK_PUSH_ROOM,
FOREST_TEMPLE_MQ_GS_RAISED_ISLAND_COURTYARD,
FOREST_TEMPLE_MQ_GS_LEVEL_ISLAND_COURTYARD,
FOREST_TEMPLE_MQ_GS_WELL,
}, {
//Shared Locations
FOREST_TEMPLE_PHANTOM_GANON_HEART,
PHANTOM_GANON,
});
DungeonInfo FireTemple = DungeonInfo("Fire Temple", FIRE_TEMPLE_MAP, FIRE_TEMPLE_COMPASS, FIRE_TEMPLE_SMALL_KEY, FIRE_TEMPLE_KEY_RING, FIRE_TEMPLE_BOSS_KEY, 8, 5, {
//Vanilla Locations
FIRE_TEMPLE_NEAR_BOSS_CHEST,
FIRE_TEMPLE_FLARE_DANCER_CHEST,
FIRE_TEMPLE_BOSS_KEY_CHEST,
FIRE_TEMPLE_BIG_LAVA_ROOM_BLOCKED_DOOR_CHEST,
FIRE_TEMPLE_BIG_LAVA_ROOM_LOWER_OPEN_DOOR_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_LOWER_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_UPPER_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_SIDE_ROOM_CHEST,
FIRE_TEMPLE_BOULDER_MAZE_SHORTCUT_CHEST,
FIRE_TEMPLE_SCARECROW_CHEST,
FIRE_TEMPLE_MAP_CHEST,
FIRE_TEMPLE_COMPASS_CHEST,
FIRE_TEMPLE_HIGHEST_GORON_CHEST,
FIRE_TEMPLE_MEGATON_HAMMER_CHEST,
FIRE_TEMPLE_GS_SONG_OF_TIME_ROOM,
FIRE_TEMPLE_GS_BOSS_KEY_LOOP,
FIRE_TEMPLE_GS_BOULDER_MAZE,
FIRE_TEMPLE_GS_SCARECROW_TOP,
FIRE_TEMPLE_GS_SCARECROW_CLIMB,
}, {
//MQ Locations
FIRE_TEMPLE_MQ_NEAR_BOSS_CHEST,
FIRE_TEMPLE_MQ_MEGATON_HAMMER_CHEST,
FIRE_TEMPLE_MQ_COMPASS_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST,
FIRE_TEMPLE_MQ_CHEST_ON_FIRE,
FIRE_TEMPLE_MQ_MAP_ROOM_SIDE_CHEST,
FIRE_TEMPLE_MQ_MAP_CHEST,
FIRE_TEMPLE_MQ_BOSS_KEY_CHEST,
FIRE_TEMPLE_MQ_BIG_LAVA_ROOM_BLOCKED_DOOR_CHEST,
FIRE_TEMPLE_MQ_LIZALFOS_MAZE_SIDE_ROOM_CHEST,
FIRE_TEMPLE_MQ_FREESTANDING_KEY,
FIRE_TEMPLE_MQ_GS_ABOVE_FIRE_WALL_MAZE,
FIRE_TEMPLE_MQ_GS_FIRE_WALL_MAZE_CENTER,
FIRE_TEMPLE_MQ_GS_BIG_LAVA_ROOM_OPEN_DOOR,
FIRE_TEMPLE_MQ_GS_FIRE_WALL_MAZE_SIDE_ROOM,
FIRE_TEMPLE_MQ_GS_SKULL_ON_FIRE,
}, {
//Shared Locations
FIRE_TEMPLE_VOLVAGIA_HEART,
VOLVAGIA,
});
DungeonInfo WaterTemple = DungeonInfo("Water Temple", WATER_TEMPLE_MAP, WATER_TEMPLE_COMPASS, WATER_TEMPLE_SMALL_KEY, WATER_TEMPLE_KEY_RING, WATER_TEMPLE_BOSS_KEY, 6, 2, {
//Vanilla Locations
WATER_TEMPLE_MAP_CHEST,
WATER_TEMPLE_COMPASS_CHEST,
WATER_TEMPLE_TORCHES_CHEST,
WATER_TEMPLE_DRAGON_CHEST,
WATER_TEMPLE_CENTRAL_BOW_TARGET_CHEST,
WATER_TEMPLE_CENTRAL_PILLAR_CHEST,
WATER_TEMPLE_CRACKED_WALL_CHEST,
WATER_TEMPLE_BOSS_KEY_CHEST,
WATER_TEMPLE_LONGSHOT_CHEST,
WATER_TEMPLE_RIVER_CHEST,
WATER_TEMPLE_GS_BEHIND_GATE,
WATER_TEMPLE_GS_FALLING_PLATFORM_ROOM,
WATER_TEMPLE_GS_CENTRAL_PILLAR,
WATER_TEMPLE_GS_NEAR_BOSS_KEY_CHEST,
WATER_TEMPLE_GS_RIVER,
}, {
//MQ Locations
WATER_TEMPLE_MQ_CENTRAL_PILLAR_CHEST,
WATER_TEMPLE_MQ_BOSS_KEY_CHEST,
WATER_TEMPLE_MQ_LONGSHOT_CHEST,
WATER_TEMPLE_MQ_COMPASS_CHEST,
WATER_TEMPLE_MQ_MAP_CHEST,
WATER_TEMPLE_MQ_FREESTANDING_KEY,
WATER_TEMPLE_MQ_GS_BEFORE_UPPER_WATER_SWITCH,
WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA,
WATER_TEMPLE_MQ_GS_LIZALFOS_HALLWAY,
WATER_TEMPLE_MQ_GS_RIVER,
WATER_TEMPLE_MQ_GS_TRIPLE_WALL_TORCH,
}, {
//Shared Locations
WATER_TEMPLE_MORPHA_HEART,
MORPHA,
});
DungeonInfo SpiritTemple = DungeonInfo("Spirit Temple", SPIRIT_TEMPLE_MAP, SPIRIT_TEMPLE_COMPASS, SPIRIT_TEMPLE_SMALL_KEY, SPIRIT_TEMPLE_KEY_RING, SPIRIT_TEMPLE_BOSS_KEY, 5, 7, {
//Vanilla Locations
SPIRIT_TEMPLE_CHILD_BRIDGE_CHEST,
SPIRIT_TEMPLE_CHILD_EARLY_TORCHES_CHEST,
SPIRIT_TEMPLE_COMPASS_CHEST,
SPIRIT_TEMPLE_EARLY_ADULT_RIGHT_CHEST,
SPIRIT_TEMPLE_FIRST_MIRROR_LEFT_CHEST,
SPIRIT_TEMPLE_FIRST_MIRROR_RIGHT_CHEST,
SPIRIT_TEMPLE_MAP_CHEST,
SPIRIT_TEMPLE_CHILD_CLIMB_NORTH_CHEST,
SPIRIT_TEMPLE_CHILD_CLIMB_EAST_CHEST,
SPIRIT_TEMPLE_SUN_BLOCK_ROOM_CHEST,
SPIRIT_TEMPLE_STATUE_ROOM_HAND_CHEST,
SPIRIT_TEMPLE_STATUE_ROOM_NORTHEAST_CHEST,
SPIRIT_TEMPLE_NEAR_FOUR_ARMOS_CHEST,
SPIRIT_TEMPLE_HALLWAY_LEFT_INVISIBLE_CHEST,
SPIRIT_TEMPLE_HALLWAY_RIGHT_INVISIBLE_CHEST,
SPIRIT_TEMPLE_BOSS_KEY_CHEST,
SPIRIT_TEMPLE_TOPMOST_CHEST,
SPIRIT_TEMPLE_GS_HALL_AFTER_SUN_BLOCK_ROOM,
SPIRIT_TEMPLE_GS_BOULDER_ROOM,
SPIRIT_TEMPLE_GS_LOBBY,
SPIRIT_TEMPLE_GS_SUN_ON_FLOOR_ROOM,
SPIRIT_TEMPLE_GS_METAL_FENCE,
}, {
//MQ Locations
SPIRIT_TEMPLE_MQ_ENTRANCE_FRONT_LEFT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_BACK_RIGHT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_FRONT_RIGHT_CHEST,
SPIRIT_TEMPLE_MQ_ENTRANCE_BACK_LEFT_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_HAMMER_SWITCH_CHEST,
SPIRIT_TEMPLE_MQ_MAP_CHEST,
SPIRIT_TEMPLE_MQ_MAP_ROOM_ENEMY_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_CLIMB_NORTH_CHEST,
SPIRIT_TEMPLE_MQ_CHILD_CLIMB_SOUTH_CHEST,
SPIRIT_TEMPLE_MQ_COMPASS_CHEST,
SPIRIT_TEMPLE_MQ_STATUE_ROOM_LULLABY_CHEST,
SPIRIT_TEMPLE_MQ_STATUE_ROOM_INVISIBLE_CHEST,
SPIRIT_TEMPLE_MQ_SILVER_BLOCK_HALLWAY_CHEST,
SPIRIT_TEMPLE_MQ_SUN_BLOCK_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_SYMPHONY_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_LEEVER_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_BEAMOS_ROOM_CHEST,
SPIRIT_TEMPLE_MQ_CHEST_SWITCH_CHEST,
SPIRIT_TEMPLE_MQ_BOSS_KEY_CHEST,
SPIRIT_TEMPLE_MQ_MIRROR_PUZZLE_INVISIBLE_CHEST,
SPIRIT_TEMPLE_MQ_GS_SYMPHONY_ROOM,
SPIRIT_TEMPLE_MQ_GS_LEEVER_ROOM,
SPIRIT_TEMPLE_MQ_GS_NINE_THRONES_ROOM_WEST,
SPIRIT_TEMPLE_MQ_GS_NINE_THRONES_ROOM_NORTH,
SPIRIT_TEMPLE_MQ_GS_SUN_BLOCK_ROOM,
}, {
//Shared Locations
SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST,
SPIRIT_TEMPLE_MIRROR_SHIELD_CHEST,
SPIRIT_TEMPLE_TWINROVA_HEART,
TWINROVA,
});
DungeonInfo ShadowTemple = DungeonInfo("Shadow Temple", SHADOW_TEMPLE_MAP, SHADOW_TEMPLE_COMPASS, SHADOW_TEMPLE_SMALL_KEY, SHADOW_TEMPLE_KEY_RING, SHADOW_TEMPLE_BOSS_KEY, 5, 6, {
//Vanilla Locations
SHADOW_TEMPLE_MAP_CHEST,
SHADOW_TEMPLE_HOVER_BOOTS_CHEST,
SHADOW_TEMPLE_COMPASS_CHEST,
SHADOW_TEMPLE_EARLY_SILVER_RUPEE_CHEST,
SHADOW_TEMPLE_INVISIBLE_BLADES_VISIBLE_CHEST,
SHADOW_TEMPLE_INVISIBLE_BLADES_INVISIBLE_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_LOWER_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_UPPER_CHEST,
SHADOW_TEMPLE_FALLING_SPIKES_SWITCH_CHEST,
SHADOW_TEMPLE_INVISIBLE_SPIKES_CHEST,
SHADOW_TEMPLE_WIND_HINT_CHEST,
SHADOW_TEMPLE_AFTER_WIND_ENEMY_CHEST,
SHADOW_TEMPLE_AFTER_WIND_HIDDEN_CHEST,
SHADOW_TEMPLE_SPIKE_WALLS_LEFT_CHEST,
SHADOW_TEMPLE_BOSS_KEY_CHEST,
SHADOW_TEMPLE_INVISIBLE_FLOORMASTER_CHEST,
SHADOW_TEMPLE_FREESTANDING_KEY,
SHADOW_TEMPLE_GS_SINGLE_GIANT_POT,
SHADOW_TEMPLE_GS_FALLING_SPIKES_ROOM,
SHADOW_TEMPLE_GS_TRIPLE_GIANT_POT,
SHADOW_TEMPLE_GS_LIKE_LIKE_ROOM,
SHADOW_TEMPLE_GS_NEAR_SHIP,
}, {
//MQ Locations
SHADOW_TEMPLE_MQ_COMPASS_CHEST,
SHADOW_TEMPLE_MQ_HOVER_BOOTS_CHEST,
SHADOW_TEMPLE_MQ_EARLY_GIBDOS_CHEST,
SHADOW_TEMPLE_MQ_MAP_CHEST,
SHADOW_TEMPLE_MQ_BEAMOS_SILVER_RUPEES_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_SWITCH_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_LOWER_CHEST,
SHADOW_TEMPLE_MQ_FALLING_SPIKES_UPPER_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_SPIKES_CHEST,
SHADOW_TEMPLE_MQ_BOSS_KEY_CHEST,
SHADOW_TEMPLE_MQ_SPIKE_WALLS_LEFT_CHEST,
SHADOW_TEMPLE_MQ_STALFOS_ROOM_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_BLADES_INVISIBLE_CHEST,
SHADOW_TEMPLE_MQ_INVISIBLE_BLADES_VISIBLE_CHEST,
SHADOW_TEMPLE_MQ_BOMB_FLOWER_CHEST,
SHADOW_TEMPLE_MQ_WIND_HINT_CHEST,
SHADOW_TEMPLE_MQ_AFTER_WIND_HIDDEN_CHEST,
SHADOW_TEMPLE_MQ_AFTER_WIND_ENEMY_CHEST,
SHADOW_TEMPLE_MQ_NEAR_SHIP_INVISIBLE_CHEST,
SHADOW_TEMPLE_MQ_FREESTANDING_KEY,
SHADOW_TEMPLE_MQ_GS_FALLING_SPIKES_ROOM,
SHADOW_TEMPLE_MQ_GS_WIND_HINT_ROOM,
SHADOW_TEMPLE_MQ_GS_AFTER_WIND,
SHADOW_TEMPLE_MQ_GS_AFTER_SHIP,
SHADOW_TEMPLE_MQ_GS_NEAR_BOSS,
}, {
//Shared Locations
SHADOW_TEMPLE_BONGO_BONGO_HEART,
BONGO_BONGO,
});
DungeonInfo BottomOfTheWell = DungeonInfo("Bottom of the Well", BOTTOM_OF_THE_WELL_MAP, BOTTOM_OF_THE_WELL_COMPASS, BOTTOM_OF_THE_WELL_SMALL_KEY, BOTTOM_OF_THE_WELL_KEY_RING, NONE, 3, 2, {
//Vanilla Locations
BOTTOM_OF_THE_WELL_FRONT_LEFT_FAKE_WALL_CHEST,
BOTTOM_OF_THE_WELL_FRONT_CENTER_BOMBABLE_CHEST,
BOTTOM_OF_THE_WELL_RIGHT_BOTTOM_FAKE_WALL_CHEST,
BOTTOM_OF_THE_WELL_COMPASS_CHEST,
BOTTOM_OF_THE_WELL_CENTER_SKULLTULA_CHEST,
BOTTOM_OF_THE_WELL_BACK_LEFT_BOMBABLE_CHEST,
BOTTOM_OF_THE_WELL_LENS_OF_TRUTH_CHEST,
BOTTOM_OF_THE_WELL_INVISIBLE_CHEST,
BOTTOM_OF_THE_WELL_UNDERWATER_FRONT_CHEST,
BOTTOM_OF_THE_WELL_UNDERWATER_LEFT_CHEST,
BOTTOM_OF_THE_WELL_MAP_CHEST,
BOTTOM_OF_THE_WELL_FIRE_KEESE_CHEST,
BOTTOM_OF_THE_WELL_LIKE_LIKE_CHEST,
BOTTOM_OF_THE_WELL_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_GS_LIKE_LIKE_CAGE,
BOTTOM_OF_THE_WELL_GS_EAST_INNER_ROOM,
BOTTOM_OF_THE_WELL_GS_WEST_INNER_ROOM,
}, {
//MQ Locations
BOTTOM_OF_THE_WELL_MQ_MAP_CHEST,
BOTTOM_OF_THE_WELL_MQ_LENS_OF_TRUTH_CHEST,
BOTTOM_OF_THE_WELL_MQ_COMPASS_CHEST,
BOTTOM_OF_THE_WELL_MQ_DEAD_HAND_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_MQ_EAST_INNER_ROOM_FREESTANDING_KEY,
BOTTOM_OF_THE_WELL_MQ_GS_BASEMENT,
BOTTOM_OF_THE_WELL_MQ_GS_COFFIN_ROOM,
BOTTOM_OF_THE_WELL_MQ_GS_WEST_INNER_ROOM,
}, {});
DungeonInfo IceCavern = DungeonInfo("Ice Cavern", ICE_CAVERN_MAP, ICE_CAVERN_COMPASS, NONE, NONE, NONE, 0, 0, {
//Vanilla Locations
ICE_CAVERN_MAP_CHEST,
ICE_CAVERN_COMPASS_CHEST,
ICE_CAVERN_IRON_BOOTS_CHEST,
ICE_CAVERN_FREESTANDING_POH,
ICE_CAVERN_GS_PUSH_BLOCK_ROOM,
ICE_CAVERN_GS_SPINNING_SCYTHE_ROOM,
ICE_CAVERN_GS_HEART_PIECE_ROOM,
}, {
//MQ Locations
ICE_CAVERN_MQ_IRON_BOOTS_CHEST,
ICE_CAVERN_MQ_COMPASS_CHEST,
ICE_CAVERN_MQ_MAP_CHEST,
ICE_CAVERN_MQ_FREESTANDING_POH,
ICE_CAVERN_MQ_GS_SCARECROW,
ICE_CAVERN_MQ_GS_ICE_BLOCK,
ICE_CAVERN_MQ_GS_RED_ICE,
}, {
//Shared Locations
SHEIK_IN_ICE_CAVERN,
});
DungeonInfo GerudoTrainingGrounds = DungeonInfo("Gerudo Training Grounds", NONE, NONE, GERUDO_TRAINING_GROUNDS_SMALL_KEY, GERUDO_TRAINING_GROUNDS_KEY_RING, NONE, 9, 3, {
//Vanilla Locations
GERUDO_TRAINING_GROUNDS_LOBBY_LEFT_CHEST,
GERUDO_TRAINING_GROUNDS_LOBBY_RIGHT_CHEST,
GERUDO_TRAINING_GROUNDS_STALFOS_CHEST,
GERUDO_TRAINING_GROUNDS_BEAMOS_CHEST,
GERUDO_TRAINING_GROUNDS_HIDDEN_CEILING_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_PATH_FINAL_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_RIGHT_CENTRAL_CHEST,
GERUDO_TRAINING_GROUNDS_MAZE_RIGHT_SIDE_CHEST,
GERUDO_TRAINING_GROUNDS_UNDERWATER_SILVER_RUPEE_CHEST,
GERUDO_TRAINING_GROUNDS_HAMMER_ROOM_CLEAR_CHEST,
GERUDO_TRAINING_GROUNDS_HAMMER_ROOM_SWITCH_CHEST,
GERUDO_TRAINING_GROUNDS_EYE_STATUE_CHEST,
GERUDO_TRAINING_GROUNDS_NEAR_SCARECROW_CHEST,
GERUDO_TRAINING_GROUNDS_BEFORE_HEAVY_BLOCK_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_HEAVY_BLOCK_FOURTH_CHEST,
GERUDO_TRAINING_GROUNDS_FREESTANDING_KEY,
}, {
//MQ Locations
GERUDO_TRAINING_GROUNDS_MQ_LOBBY_RIGHT_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_LOBBY_LEFT_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_FIRST_IRON_KNUCKLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_BEFORE_HEAVY_BLOCK_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_EYE_STATUE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_FLAME_CIRCLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_SECOND_IRON_KNUCKLE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_DINOLFOS_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_ICE_ARROWS_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_RIGHT_CENTRAL_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_FIRST_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_RIGHT_SIDE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_THIRD_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_MAZE_PATH_SECOND_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_HIDDEN_CEILING_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_UNDERWATER_SILVER_RUPEE_CHEST,
GERUDO_TRAINING_GROUNDS_MQ_HEAVY_BLOCK_CHEST,
}, {});
DungeonInfo GanonsCastle = DungeonInfo("Ganon's Castle", NONE, NONE, GANONS_CASTLE_SMALL_KEY, GANONS_CASTLE_KEY_RING, GANONS_CASTLE_BOSS_KEY, 2, 3, {
//Vanilla Locations
GANONS_CASTLE_FOREST_TRIAL_CHEST,
GANONS_CASTLE_WATER_TRIAL_LEFT_CHEST,
GANONS_CASTLE_WATER_TRIAL_RIGHT_CHEST,
GANONS_CASTLE_SHADOW_TRIAL_FRONT_CHEST,
GANONS_CASTLE_SHADOW_TRIAL_GOLDEN_GAUNTLETS_CHEST,
GANONS_CASTLE_SPIRIT_TRIAL_CRYSTAL_SWITCH_CHEST,
GANONS_CASTLE_SPIRIT_TRIAL_INVISIBLE_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_FIRST_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_SECOND_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_THIRD_LEFT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_FIRST_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_SECOND_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_THIRD_RIGHT_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_INVISIBLE_ENEMIES_CHEST,
GANONS_CASTLE_LIGHT_TRIAL_LULLABY_CHEST,
GANONS_CASTLE_DEKU_SCRUB_LEFT,
GANONS_CASTLE_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_DEKU_SCRUB_RIGHT,
}, {
//MQ Locations
GANONS_CASTLE_MQ_WATER_TRIAL_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_FROZEN_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_LIGHT_TRIAL_LULLABY_CHEST,
GANONS_CASTLE_MQ_SHADOW_TRIAL_BOMB_FLOWER_CHEST,
GANONS_CASTLE_MQ_SHADOW_TRIAL_EYE_SWITCH_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_GOLDEN_GAUNTLETS_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_BACK_RIGHT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_BACK_LEFT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_SUN_FRONT_LEFT_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_FIRST_CHEST,
GANONS_CASTLE_MQ_SPIRIT_TRIAL_INVISIBLE_CHEST,
GANONS_CASTLE_MQ_FOREST_TRIAL_FREESTANDING_KEY,
GANONS_CASTLE_MQ_DEKU_SCRUB_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_LEFT,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER,
GANONS_CASTLE_MQ_DEKU_SCRUB_CENTER_RIGHT,
GANONS_CASTLE_MQ_DEKU_SCRUB_LEFT,
}, {
//Shared Locations
GANONS_TOWER_BOSS_KEY_CHEST,
GANON,
});
const DungeonArray dungeonList = {
&DekuTree,
&DodongosCavern,
&JabuJabusBelly,
&ForestTemple,
&FireTemple,
&WaterTemple,
&SpiritTemple,
&ShadowTemple,
&BottomOfTheWell,
&IceCavern,
&GerudoTrainingGrounds,
&GanonsCastle,
};
} //namespace Dungeon

View File

@ -0,0 +1,104 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "keys.hpp"
namespace Dungeon {
class DungeonInfo {
public:
DungeonInfo(std::string name_, uint32_t map_, uint32_t compass_, uint32_t smallKey_, uint32_t keyRing_, uint32_t bossKey_,
uint8_t vanillaKeyCount_, uint8_t mqKeyCount_,
std::vector<uint32_t> vanillaLocations_,
std::vector<uint32_t> mqLocations_,
std::vector<uint32_t> sharedLocations_);
~DungeonInfo();
const std::string& GetName() const {
return name;
}
void SetMQ() {
masterQuest = true;
}
void ClearMQ() {
masterQuest = false;
}
bool IsMQ() const {
return masterQuest;
}
void SetKeyRing() {
hasKeyRing = true;
}
void ClearKeyRing() {
hasKeyRing = false;
}
bool HasKeyRing() const {
return hasKeyRing;
}
bool IsVanilla() const {
return !masterQuest;
}
uint8_t GetSmallKeyCount() const {
return (masterQuest) ? mqKeyCount : vanillaKeyCount;
}
uint32_t GetSmallKey() const;
uint32_t GetKeyRing() const;
uint32_t GetMap() const;
uint32_t GetCompass() const;
uint32_t GetBossKey() const;
void PlaceVanillaMap();
void PlaceVanillaCompass();
void PlaceVanillaBossKey();
void PlaceVanillaSmallKeys();
// Gets the chosen dungeon locations for a playthrough (so either MQ or Vanilla)
std::vector<uint32_t> GetDungeonLocations() const;
// Gets all dungeon locations (MQ + Vanilla)
std::vector<uint32_t> GetEveryLocation() const;
private:
std::string name;
uint32_t map;
uint32_t compass;
uint32_t smallKey;
uint32_t keyRing;
uint32_t bossKey;
uint8_t vanillaKeyCount;
uint8_t mqKeyCount;
bool masterQuest = false;
bool hasKeyRing = false;
std::vector<uint32_t> vanillaLocations;
std::vector<uint32_t> mqLocations;
std::vector<uint32_t> sharedLocations;
};
extern DungeonInfo DekuTree;
extern DungeonInfo DodongosCavern;
extern DungeonInfo JabuJabusBelly;
extern DungeonInfo ForestTemple;
extern DungeonInfo FireTemple;
extern DungeonInfo WaterTemple;
extern DungeonInfo SpiritTemple;
extern DungeonInfo ShadowTemple;
extern DungeonInfo BottomOfTheWell;
extern DungeonInfo IceCavern;
extern DungeonInfo GerudoTrainingGrounds;
extern DungeonInfo GanonsCastle;
using DungeonArray = std::array<DungeonInfo*, 12>;
extern const DungeonArray dungeonList;
} // namespace Dungeon

View File

@ -0,0 +1,877 @@
#include "entrance.hpp"
#include "fill.hpp"
#include "settings.hpp"
#include "item_list.hpp"
#include "item_pool.hpp"
#include "item_location.hpp"
#include "debug.hpp"
#include "spoiler_log.hpp"
#include "hints.hpp"
#include "location_access.hpp"
#include <vector>
#include <utility>
#include <set>
#include <map>
#include <Lib/spdlog/include/spdlog/spdlog.h>
std::list<EntranceOverride> entranceOverrides = {};
bool noRandomEntrances = false;
static bool entranceShuffleFailure = false;
static int totalRandomizableEntrances = 0;
static int curNumRandomizedEntrances = 0;
typedef struct {
EntranceType type;
uint32_t parentRegion;
uint32_t connectedRegion;
int16_t index;
int16_t blueWarp;
} EntranceLinkInfo;
//primary, secondary
using EntranceInfoPair = std::pair<EntranceLinkInfo, EntranceLinkInfo>;
using EntrancePair = std::pair<Entrance*, Entrance*>;
//The entrance randomization algorithm used here is a direct copy of
//the algorithm used in the original N64 randomizer (except now in C++ instead
//of python). It may be easier to understand the algorithm by looking at the
//base randomizer's code instead:
// https://github.com/TestRunnerSRL/OoT-Randomizer/blob/Dev/EntranceShuffle.py
// Updates the user on how many entrances are currently shuffled
static void DisplayEntranceProgress() {
std::string dots = ".";
float progress = (float)curNumRandomizedEntrances / (float)totalRandomizableEntrances;
if (progress > 0.33) {
dots += ".";
} else {
dots += " ";
}
if (progress > 0.66) {
dots += ".";
} else {
dots += " ";
}
printf("\x1b[7;29H%s", dots.c_str());
#ifdef ENABLE_DEBUG
if (curNumRandomizedEntrances == totalRandomizableEntrances) {
Areas::DumpWorldGraph("Finish Validation");
}
#endif
}
void SetAllEntrancesData(std::vector<EntranceInfoPair>& entranceShuffleTable) {
for (auto& entrancePair: entranceShuffleTable) {
auto& forwardEntry = entrancePair.first;
auto& returnEntry = entrancePair.second;
//set data
Entrance* forwardEntrance = AreaTable(forwardEntry.parentRegion)->GetExit(forwardEntry.connectedRegion);
forwardEntrance->SetIndex(forwardEntry.index);
forwardEntrance->SetBlueWarp(forwardEntry.blueWarp);
forwardEntrance->SetType(forwardEntry.type);
forwardEntrance->SetAsPrimary();
// if type == 'Grotto':
// forward_entrance.data['index'] = 0x0700 + forward_entrance.data['grotto_id']
if (returnEntry.parentRegion != NONE) {
Entrance* returnEntrance = AreaTable(returnEntry.parentRegion)->GetExit(returnEntry.connectedRegion);
returnEntrance->SetIndex(returnEntry.index);
returnEntrance->SetBlueWarp(returnEntry.blueWarp);
returnEntrance->SetType(returnEntry.type);
forwardEntrance->BindTwoWay(returnEntrance);
// if type == 'Grotto':
// return_entrance.data['index'] = 0x0800 + return_entrance.data['grotto_id']
}
}
}
static std::vector<Entrance*> AssumeEntrancePool(std::vector<Entrance*>& entrancePool) {
std::vector<Entrance*> assumedPool = {};
for (Entrance* entrance : entrancePool) {
Entrance* assumedForward = entrance->AssumeReachable();
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
Entrance* assumedReturn = entrance->GetReverse()->AssumeReachable();
//mixed pool assumption stuff
assumedForward->BindTwoWay(assumedReturn);
}
assumedPool.push_back(assumedForward);
}
return assumedPool;
}
//returns restrictive entrances and soft entrances in an array of size 2 (restrictive vector is index 0, soft is index 1)
static std::array<std::vector<Entrance*>, 2> SplitEntrancesByRequirements(std::vector<Entrance*>& entrancesToSplit, std::vector<Entrance*>& assumedEntrances) {
//First, disconnect all root assumed entrances and save which regions they were originally connected to, so we can reconnect them later
std::map<Entrance*, uint32_t> originalConnectedRegions = {};
std::set<Entrance*> entrancesToDisconnect = {};
for (Entrance* entrance : assumedEntrances) {
entrancesToDisconnect.insert(entrance);
if (entrance->GetReverse() != nullptr) {
entrancesToDisconnect.insert(entrance->GetReverse());
}
}
//disconnect each entrance temporarily to find restrictive vs soft entrances
//soft entrances are ones that can be accessed by both ages (child/adult) at both times of day (day/night)
//restrictive entrances are ones that do not meet this criteria
for (Entrance* entrance : entrancesToDisconnect) {
if (entrance->GetConnectedRegionKey() != NONE) {
originalConnectedRegions[entrance] = entrance->Disconnect();
}
}
std::vector<Entrance*> restrictiveEntrances = {};
std::vector<Entrance*> softEntrances = {};
Logic::LogicReset();
// //Apply the effects of all advancement items to search for entrance accessibility
std::vector<uint32_t> items = FilterFromPool(ItemPool, [](const auto i){ return ItemTable(i).IsAdvancement();});
for (uint32_t unplacedItem : items) {
ItemTable(unplacedItem).ApplyEffect();
}
// run a search to see what's accessible
GetAccessibleLocations({});
for (Entrance* entrance : entrancesToSplit) {
// if an entrance is accessible at all times of day by both ages, it's a soft entrance with no restrictions
if (entrance->ConditionsMet(true)) {
softEntrances.push_back(entrance);
} else {
restrictiveEntrances.push_back(entrance);
}
}
//Reconnect all disconnected entrances
for (Entrance* entrance : entrancesToDisconnect) {
entrance->Connect(originalConnectedRegions[entrance]);
}
return {restrictiveEntrances, softEntrances};
}
static bool AreEntrancesCompatible(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
//Entrances shouldn't connect to their own scene, fail in this situation
if (entrance->GetParentRegion()->scene != "" && entrance->GetParentRegion()->scene == target->GetConnectedRegion()->scene) {
auto message = "Entrance " + entrance->GetName() + " attempted to connect with own scene target " + target->to_string() + ". Connection failed.\n";
SPDLOG_INFO(message);
return false;
}
//one-way entrance stuff
return true;
}
//Change connections between an entrance and a target assumed entrance, in order to test the connections afterwards if necessary
static void ChangeConnections(Entrance* entrance, Entrance* targetEntrance) {
auto message = "Attempting to connect " + entrance->GetName() + " to " + targetEntrance->to_string() + "\n";
SPDLOG_INFO(message);
entrance->Connect(targetEntrance->Disconnect());
entrance->SetReplacement(targetEntrance->GetReplacement());
if (entrance->GetReverse() != nullptr /*&& entrances aren't decoupled*/) {
targetEntrance->GetReplacement()->GetReverse()->Connect(entrance->GetReverse()->GetAssumed()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(entrance->GetReverse());
}
}
static void RestoreConnections(Entrance* entrance, Entrance* targetEntrance) {
targetEntrance->Connect(entrance->Disconnect());
entrance->SetReplacement(nullptr);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
entrance->GetReverse()->GetAssumed()->Connect(targetEntrance->GetReplacement()->GetReverse()->Disconnect());
targetEntrance->GetReplacement()->GetReverse()->SetReplacement(nullptr);
}
}
static void DeleteTargetEntrance(Entrance* targetEntrance) {
if (targetEntrance->GetConnectedRegionKey() != NONE) {
targetEntrance->Disconnect();
}
if (targetEntrance->GetParentRegionKey() != NONE) {
targetEntrance->GetParentRegion()->RemoveExit(targetEntrance);
targetEntrance->SetParentRegion(NONE);
}
}
static void ConfirmReplacement(Entrance* entrance, Entrance* targetEntrance) {
DeleteTargetEntrance(targetEntrance);
if (entrance->GetReverse() != nullptr /*&& entrances are not decoupled*/) {
auto replacedReverse = targetEntrance->GetReplacement()->GetReverse();
DeleteTargetEntrance(replacedReverse->GetReverse()->GetAssumed());
}
}
// Returns whether or not we can affirm the entrance can never be accessed as the given age
static bool EntranceUnreachableAs(Entrance* entrance, uint8_t age, std::vector<Entrance*>& alreadyChecked) {
if (entrance == nullptr) {
SPDLOG_INFO("Entrance is nullptr in EntranceUnreachableAs()");
return true;
}
alreadyChecked.push_back(entrance);
auto type = entrance->GetType();
// The following cases determine when we say an entrance is not safe to affirm unreachable as the given age
if (type == EntranceType::WarpSong || type == EntranceType::Overworld) {
// Note that we consider all overworld entrances as potentially accessible as both ages, to be completely safe
return false;
} else if (type == EntranceType::OwlDrop) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == KF_LINKS_HOUSE) {
return age == AGE_ADULT;
} else if (type == EntranceType::Spawn && entrance->GetConnectedRegionKey() == TEMPLE_OF_TIME) {
return age == AGE_CHILD;
}
// Other entrances such as Interior, Dungeon or Grotto are fine unless they have a parent which is one of the above
// cases Recursively check parent entrances to verify that they are also not reachable as the wrong age
auto& parentEntrances = entrance->GetParentRegion()->entrances;
for (Entrance* parentEntrance : parentEntrances) {
// if parentEntrance is in alreadyChecked, then continue
if (ElementInContainer(parentEntrance, alreadyChecked)) {
continue;
}
bool unreachable = EntranceUnreachableAs(parentEntrance, age, alreadyChecked);
if (!unreachable) {
return false;
}
}
return true;
}
static bool ValidateWorld(Entrance* entrancePlaced) {
SPDLOG_INFO("Validating world\n");
//check certain conditions when certain types of ER are enabled
EntranceType type = EntranceType::None;
if (entrancePlaced != nullptr) {
type = entrancePlaced->GetType();
}
bool checkPoeCollectorAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::Interior || type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
bool checkOtherEntranceAccess = (Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL) /*|| Settings::ShuffleOverworldSpawns*/) && (entrancePlaced == nullptr /*|| Settings::MixedEntrancePools.IsNot(MIXEDENTRANCES_OFF)*/ ||
type == EntranceType::SpecialInterior || type == EntranceType::Overworld || type == EntranceType::Spawn || type == EntranceType::WarpSong || type == EntranceType::OwlDrop);
// Check to make sure all locations are still reachable
Logic::LogicReset();
GetAccessibleLocations({}, SearchMode::ValidateWorld, "", checkPoeCollectorAccess, checkOtherEntranceAccess);
// if not world.decouple_entrances:
// Unless entrances are decoupled, we don't want the player to end up through certain entrances as the wrong age
// This means we need to hard check that none of the relevant entrances are ever reachable as that age
// This is mostly relevant when mixing entrance pools or shuffling special interiors (such as windmill or kak potion shop)
// Warp Songs and Overworld Spawns can also end up inside certain indoors so those need to be handled as well
std::array<std::string, 2> childForbidden = {"OGC Great Fairy Fountain -> Castle Grounds", "GV Carpenter Tent -> GV Fortress Side"};
std::array<std::string, 2> adultForbidden = {"HC Great Fairy Fountain -> Castle Grounds", "HC Storms Grotto -> Castle Grounds"};
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (auto& entrance: allShuffleableEntrances) {
std::vector<Entrance*> alreadyChecked = {};
if (entrance->IsShuffled()) {
if (entrance->GetReplacement() != nullptr) {
auto replacementName = entrance->GetReplacement()->GetName();
alreadyChecked.push_back(entrance->GetReplacement()->GetReverse());
if (ElementInContainer(replacementName, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential child access\n";
SPDLOG_INFO(message);
return false;
} else if (ElementInContainer(replacementName, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = replacementName + " is replaced by an entrance with a potential adult access\n";
SPDLOG_INFO(message);
return false;
}
}
} else {
auto name = entrance->GetName();
alreadyChecked.push_back(entrance->GetReverse());
if (ElementInContainer(name, childForbidden) && !EntranceUnreachableAs(entrance, AGE_CHILD, alreadyChecked)) {
auto message = name + " is potentially accessible as child\n";
SPDLOG_INFO(message);
return false;
} else if (ElementInContainer(name, adultForbidden) && !EntranceUnreachableAs(entrance, AGE_ADULT, alreadyChecked)) {
auto message = name + " is potentially accessible as adult\n";
SPDLOG_INFO(message);
return false;
}
}
}
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF) && Settings::GossipStoneHints.IsNot(HINTS_NO_HINTS) &&
(entrancePlaced == nullptr || type == EntranceType::Interior || type == EntranceType::SpecialInterior)) {
//When cows are shuffled, ensure both Impa's House entrances are in the same hint area because the cow is reachable from both sides
if (Settings::ShuffleCows) {
auto impasHouseFrontHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE);
auto impasHouseBackHintRegion = GetHintRegionHintKey(KAK_IMPAS_HOUSE_BACK);
if (impasHouseFrontHintRegion != NONE && impasHouseBackHintRegion != NONE && impasHouseBackHintRegion != LINKS_POCKET && impasHouseFrontHintRegion != LINKS_POCKET && impasHouseBackHintRegion != impasHouseFrontHintRegion) {
auto message = "Kak Impas House entrances are not in the same hint area\n";
SPDLOG_INFO(message);
return false;
}
}
}
// If all locations aren't reachable, that means that one of the conditions failed when searching
if (!allLocationsReachable) {
if (checkOtherEntranceAccess) {
// At least one valid starting region with all basic refills should be reachable without using any items at the beginning of the seed
if (!AreaTable(KOKIRI_FOREST)->HasAccess() && !AreaTable(KAKARIKO_VILLAGE)->HasAccess()) {
SPDLOG_INFO("Invalid starting area\n");
return false;
}
// Check that a region where time passes is always reachable as both ages without having collected any items
if (!Areas::HasTimePassAccess(AGE_CHILD) || !Areas::HasTimePassAccess(AGE_ADULT)) {
SPDLOG_INFO("Time passing is not guaranteed as both ages\n");
return false;
}
// The player should be able to get back to ToT after going through time, without having collected any items
// This is important to ensure that the player never loses access to the pedestal after going through time
if (Settings::ResolvedStartingAge == AGE_CHILD && !AreaTable(TEMPLE_OF_TIME)->Adult()) {
SPDLOG_INFO("Path to Temple of Time as adult is not guaranteed\n");
return false;
} else if (Settings::ResolvedStartingAge == AGE_ADULT && !AreaTable(TEMPLE_OF_TIME)->Child()) {
SPDLOG_INFO("Path to Temple of Time as child is not guaranteed\n");
return false;
}
}
// The Big Poe shop should always be accessible as adult without the need to use any bottles
// This is important to ensure that players can never lock their only bottles by filling them with Big Poes they can't sell
if (checkPoeCollectorAccess) {
if (!AreaTable(MARKET_GUARD_HOUSE)->Adult()) {
SPDLOG_INFO("Big Poe Shop access is not guarenteed as adult\n");
return false;
}
}
SPDLOG_INFO("All Locations NOT REACHABLE\n");
return false;
}
return true;
}
static bool ReplaceEntrance(Entrance* entrance, Entrance* target, std::vector<EntrancePair>& rollbacks) {
if (!AreEntrancesCompatible(entrance, target, rollbacks)) {
return false;
}
ChangeConnections(entrance, target);
if (ValidateWorld(entrance)) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
rollbacks.push_back(EntrancePair{entrance, target});
curNumRandomizedEntrances++;
DisplayEntranceProgress();
return true;
} else {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Dumping World Graph at " + ticks + "\n";
//PlacementLog_Msg(message);
//Areas::DumpWorldGraph(ticks);
#endif
if (entrance->GetConnectedRegionKey() != NONE) {
RestoreConnections(entrance, target);
}
}
DisplayEntranceProgress();
return false;
}
// Shuffle entrances by placing them instead of entrances in the provided target entrances list
static bool ShuffleEntrances(std::vector<Entrance*>& entrances, std::vector<Entrance*>& targetEntrances, std::vector<EntrancePair>& rollbacks) {
Shuffle(entrances);
//place all entrances in the pool, validating after every placement
for (Entrance* entrance : entrances) {
if (entrance->GetConnectedRegionKey() != NONE) {
continue;
}
Shuffle(targetEntrances);
for (Entrance* target : targetEntrances) {
if (target->GetConnectedRegionKey() == NONE) {
continue;
}
if (ReplaceEntrance(entrance, target, rollbacks)) {
break;
}
}
if (entrance->GetConnectedRegionKey() == NONE) {
return false;
}
}
//all entrances were validly connected
return true;
}
static void ShuffleEntrancePool(std::vector<Entrance*>& entrancePool, std::vector<Entrance*>& targetEntrances) {
noRandomEntrances = false;
auto splitEntrances = SplitEntrancesByRequirements(entrancePool, targetEntrances);
auto& restrictiveEntrances = splitEntrances[0];
auto& softEntrances = splitEntrances[1];
int retries = 20;
while (retries > 0) {
if (retries != 20) {
#ifdef ENABLE_DEBUG
std::string ticks = std::to_string(svcGetSystemTick());
auto message = "Failed to connect entrances. Retrying " + std::to_string(retries) + " more times.\nDumping World Graph at " + ticks + "\n";
PlacementLog_Msg(message);
Areas::DumpWorldGraph(ticks);
#endif
}
retries--;
std::vector<EntrancePair> rollbacks = {};
//Shuffle Restrictive Entrances first while more regions are available in
//order to heavily reduce the chances of the placement failing
bool success = ShuffleEntrances(restrictiveEntrances, targetEntrances, rollbacks);
if (success) {
success = ShuffleEntrances(softEntrances, targetEntrances, rollbacks);
if(!success) {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
} else {
for (auto& pair : rollbacks) {
RestoreConnections(pair.first, pair.second);
curNumRandomizedEntrances--;
}
continue;
}
//If there are no issues, log the connections and continue
for (auto& pair : rollbacks) {
ConfirmReplacement(pair.first, pair.second);
}
break;
}
if (retries <= 0) {
SPDLOG_INFO("Entrance placement attempt count exceeded. Restarting randomization completely");
entranceShuffleFailure = true;
}
}
//Process for setting up the shuffling of all entrances to be shuffled
int ShuffleAllEntrances() {
totalRandomizableEntrances = 0;
curNumRandomizedEntrances = 0;
std::vector<EntranceInfoPair> entranceShuffleTable = {
//Parent Region Connected Region index blue warp
{{EntranceType::Dungeon, KF_OUTSIDE_DEKU_TREE, DEKU_TREE_ENTRYWAY, 0x0000},
{EntranceType::Dungeon, DEKU_TREE_ENTRYWAY, KF_OUTSIDE_DEKU_TREE, 0x0209, 0x0457}},
{{EntranceType::Dungeon, DEATH_MOUNTAIN_TRAIL, DODONGOS_CAVERN_ENTRYWAY, 0x0004},
{EntranceType::Dungeon, DODONGOS_CAVERN_ENTRYWAY, DEATH_MOUNTAIN_TRAIL, 0x0242, 0x047A}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, JABU_JABUS_BELLY_ENTRYWAY, 0x0028},
{EntranceType::Dungeon, JABU_JABUS_BELLY_ENTRYWAY, ZORAS_FOUNTAIN, 0x0221, 0x010E}},
{{EntranceType::Dungeon, SACRED_FOREST_MEADOW, FOREST_TEMPLE_ENTRYWAY, 0x0169},
{EntranceType::Dungeon, FOREST_TEMPLE_ENTRYWAY, SACRED_FOREST_MEADOW, 0x0215, 0x0608}},
{{EntranceType::Dungeon, DMC_CENTRAL_LOCAL, FIRE_TEMPLE_ENTRYWAY, 0x0165},
{EntranceType::Dungeon, FIRE_TEMPLE_ENTRYWAY, DMC_CENTRAL_LOCAL, 0x024A, 0x0564}},
{{EntranceType::Dungeon, LAKE_HYLIA, WATER_TEMPLE_ENTRYWAY, 0x0010},
{EntranceType::Dungeon, WATER_TEMPLE_ENTRYWAY, LAKE_HYLIA, 0x021D, 0x060C}},
{{EntranceType::Dungeon, DESERT_COLOSSUS, SPIRIT_TEMPLE_ENTRYWAY, 0x0082},
{EntranceType::Dungeon, SPIRIT_TEMPLE_ENTRYWAY, DESERT_COLOSSUS, 0x01E1, 0x0610}},
{{EntranceType::Dungeon, GRAVEYARD_WARP_PAD_REGION, SHADOW_TEMPLE_ENTRYWAY, 0x0037},
{EntranceType::Dungeon, SHADOW_TEMPLE_ENTRYWAY, GRAVEYARD_WARP_PAD_REGION, 0x0205, 0x0580}},
{{EntranceType::Dungeon, KAKARIKO_VILLAGE, BOTTOM_OF_THE_WELL_ENTRYWAY, 0x0098},
{EntranceType::Dungeon, BOTTOM_OF_THE_WELL_ENTRYWAY, KAKARIKO_VILLAGE, 0x02A6}},
{{EntranceType::Dungeon, ZORAS_FOUNTAIN, ICE_CAVERN_ENTRYWAY, 0x0088},
{EntranceType::Dungeon, ICE_CAVERN_ENTRYWAY, ZORAS_FOUNTAIN, 0x03D4}},
{{EntranceType::Dungeon, GERUDO_FORTRESS, GERUDO_TRAINING_GROUNDS_ENTRYWAY, 0x0008},
{EntranceType::Dungeon, GERUDO_TRAINING_GROUNDS_ENTRYWAY, GERUDO_FORTRESS, 0x03A8}},
{{EntranceType::GanonDungeon, GANONS_CASTLE_GROUNDS, GANONS_CASTLE_ENTRYWAY, 0x0467},
{EntranceType::GanonDungeon, GANONS_CASTLE_ENTRYWAY, GANONS_CASTLE_GROUNDS, 0x023D}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_MIDOS_HOUSE, 0x0433},
{EntranceType::Interior, KF_MIDOS_HOUSE, KOKIRI_FOREST, 0x0443}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_SARIAS_HOUSE, 0x0437},
{EntranceType::Interior, KF_SARIAS_HOUSE, KOKIRI_FOREST, 0x0447}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_HOUSE_OF_TWINS, 0x009C},
{EntranceType::Interior, KF_HOUSE_OF_TWINS, KOKIRI_FOREST, 0x033C}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KNOW_IT_ALL_HOUSE, 0x00C9},
{EntranceType::Interior, KF_KNOW_IT_ALL_HOUSE, KOKIRI_FOREST, 0x026A}},
{{EntranceType::Interior, KOKIRI_FOREST, KF_KOKIRI_SHOP, 0x00C1},
{EntranceType::Interior, KF_KOKIRI_SHOP, KOKIRI_FOREST, 0x0266}},
{{EntranceType::Interior, LAKE_HYLIA, LH_LAB, 0x0043},
{EntranceType::Interior, LH_LAB, LAKE_HYLIA, 0x03CC}},
{{EntranceType::Interior, LH_FISHING_ISLAND, LH_FISHING_HOLE, 0x045F},
{EntranceType::Interior, LH_FISHING_HOLE, LH_FISHING_ISLAND, 0x0309}},
{{EntranceType::Interior, GV_FORTRESS_SIDE, GV_CARPENTER_TENT, 0x03A0},
{EntranceType::Interior, GV_CARPENTER_TENT, GV_FORTRESS_SIDE, 0x03D0}},
{{EntranceType::Interior, MARKET_ENTRANCE, MARKET_GUARD_HOUSE, 0x007E},
{EntranceType::Interior, MARKET_GUARD_HOUSE, MARKET_ENTRANCE, 0x026E}},
{{EntranceType::Interior, THE_MARKET, MARKET_MASK_SHOP, 0x0530},
{EntranceType::Interior, MARKET_MASK_SHOP, THE_MARKET, 0x01D1}},
{{EntranceType::Interior, THE_MARKET, MARKET_BOMBCHU_BOWLING, 0x0507},
{EntranceType::Interior, MARKET_BOMBCHU_BOWLING, THE_MARKET, 0x03BC}},
{{EntranceType::Interior, THE_MARKET, MARKET_POTION_SHOP, 0x0388},
{EntranceType::Interior, MARKET_POTION_SHOP, THE_MARKET, 0x02A2}},
{{EntranceType::Interior, THE_MARKET, MARKET_TREASURE_CHEST_GAME, 0x0063},
{EntranceType::Interior, MARKET_TREASURE_CHEST_GAME, THE_MARKET, 0x01D5}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_BOMBCHU_SHOP, 0x0528},
{EntranceType::Interior, MARKET_BOMBCHU_SHOP, MARKET_BACK_ALLEY, 0x03C0}},
{{EntranceType::Interior, MARKET_BACK_ALLEY, MARKET_MAN_IN_GREEN_HOUSE, 0x043B},
{EntranceType::Interior, MARKET_MAN_IN_GREEN_HOUSE, MARKET_BACK_ALLEY, 0x0067}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_CARPENTER_BOSS_HOUSE, 0x02FD},
{EntranceType::Interior, KAK_CARPENTER_BOSS_HOUSE, KAKARIKO_VILLAGE, 0x0349}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_HOUSE_OF_SKULLTULA, 0x0550},
{EntranceType::Interior, KAK_HOUSE_OF_SKULLTULA, KAKARIKO_VILLAGE, 0x04EE}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_IMPAS_HOUSE, 0x039C},
{EntranceType::Interior, KAK_IMPAS_HOUSE, KAKARIKO_VILLAGE, 0x0345}},
{{EntranceType::Interior, KAK_IMPAS_LEDGE, KAK_IMPAS_HOUSE_BACK, 0x05C8},
{EntranceType::Interior, KAK_IMPAS_HOUSE_BACK, KAK_IMPAS_LEDGE, 0x05DC}},
{{EntranceType::Interior, KAK_BACKYARD, KAK_ODD_POTION_BUILDING, 0x0072},
{EntranceType::Interior, KAK_ODD_POTION_BUILDING, KAK_BACKYARD, 0x034D}},
{{EntranceType::Interior, THE_GRAVEYARD, GRAVEYARD_DAMPES_HOUSE, 0x030D},
{EntranceType::Interior, GRAVEYARD_DAMPES_HOUSE, THE_GRAVEYARD, 0x0355}},
{{EntranceType::Interior, GORON_CITY, GC_SHOP, 0x037C},
{EntranceType::Interior, GC_SHOP, GORON_CITY, 0x03FC}},
{{EntranceType::Interior, ZORAS_DOMAIN, ZD_SHOP, 0x0380},
{EntranceType::Interior, ZD_SHOP, ZORAS_DOMAIN, 0x03C4}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TALONS_HOUSE, 0x004F},
{EntranceType::Interior, LLR_TALONS_HOUSE, LON_LON_RANCH, 0x0378}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_STABLES, 0x02F9},
{EntranceType::Interior, LLR_STABLES, LON_LON_RANCH, 0x042F}},
{{EntranceType::Interior, LON_LON_RANCH, LLR_TOWER, 0x05D0},
{EntranceType::Interior, LLR_TOWER, LON_LON_RANCH, 0x05D4}},
{{EntranceType::Interior, THE_MARKET, MARKET_BAZAAR, 0x052C},
{EntranceType::Interior, MARKET_BAZAAR, THE_MARKET, 0x03B8}},
{{EntranceType::Interior, THE_MARKET, MARKET_SHOOTING_GALLERY, 0x016D},
{EntranceType::Interior, MARKET_SHOOTING_GALLERY, THE_MARKET, 0x01CD}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_BAZAAR, 0x00B7},
{EntranceType::Interior, KAK_BAZAAR, KAKARIKO_VILLAGE, 0x0201}},
{{EntranceType::Interior, KAKARIKO_VILLAGE, KAK_SHOOTING_GALLERY, 0x003B},
{EntranceType::Interior, KAK_SHOOTING_GALLERY, KAKARIKO_VILLAGE, 0x0463}},
{{EntranceType::Interior, DESERT_COLOSSUS, COLOSSUS_GREAT_FAIRY_FOUNTAIN, 0x0588},
{EntranceType::Interior, COLOSSUS_GREAT_FAIRY_FOUNTAIN, DESERT_COLOSSUS, 0x057C}},
{{EntranceType::Interior, HYRULE_CASTLE_GROUNDS, HC_GREAT_FAIRY_FOUNTAIN, 0x0578},
{EntranceType::Interior, HC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x0340}},
{{EntranceType::Interior, GANONS_CASTLE_GROUNDS, OGC_GREAT_FAIRY_FOUNTAIN, 0x04C2},
{EntranceType::Interior, OGC_GREAT_FAIRY_FOUNTAIN, CASTLE_GROUNDS, 0x03E8}}, //0x3E8 is an unused entrance index repruposed to differentiate between the HC and OGC fairy fountain exits (normally they both use 0x340)
{{EntranceType::Interior, DMC_LOWER_NEARBY, DMC_GREAT_FAIRY_FOUNTAIN, 0x04BE},
{EntranceType::Interior, DMC_GREAT_FAIRY_FOUNTAIN, DMC_LOWER_LOCAL, 0x0482}},
{{EntranceType::Interior, DEATH_MOUNTAIN_SUMMIT, DMT_GREAT_FAIRY_FOUNTAIN, 0x0315},
{EntranceType::Interior, DMT_GREAT_FAIRY_FOUNTAIN, DEATH_MOUNTAIN_SUMMIT, 0x045B}},
{{EntranceType::Interior, ZORAS_FOUNTAIN, ZF_GREAT_FAIRY_FOUNTAIN, 0x0371},
{EntranceType::Interior, ZF_GREAT_FAIRY_FOUNTAIN, ZORAS_FOUNTAIN, 0x0394}},
{{EntranceType::SpecialInterior, KOKIRI_FOREST, KF_LINKS_HOUSE, 0x0272},
{EntranceType::SpecialInterior, KF_LINKS_HOUSE, KOKIRI_FOREST, 0x0211}},
{{EntranceType::SpecialInterior, TOT_ENTRANCE, TEMPLE_OF_TIME, 0x0053},
{EntranceType::SpecialInterior, TEMPLE_OF_TIME, TOT_ENTRANCE, 0x0472}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_WINDMILL, 0x0453},
{EntranceType::SpecialInterior, KAK_WINDMILL, KAKARIKO_VILLAGE, 0x0351}},
{{EntranceType::SpecialInterior, KAKARIKO_VILLAGE, KAK_POTION_SHOP_FRONT, 0x0384},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_FRONT, KAKARIKO_VILLAGE, 0x044B}},
{{EntranceType::SpecialInterior, KAK_BACKYARD, KAK_POTION_SHOP_BACK, 0x03EC},
{EntranceType::SpecialInterior, KAK_POTION_SHOP_BACK, KAK_BACKYARD, 0x04FF}},
// Grotto Loads use an entrance index of 0x0700 + their grotto id. The id is used as index for the
// grottoLoadTable in src/grotto.c
// Grotto Returns use an entrance index of 0x0800 + their grotto id. The id is used as index for the
// grottoReturnTable in src/grotto.c
{{EntranceType::GrottoGrave, DESERT_COLOSSUS, COLOSSUS_GROTTO, 0x0700},
{EntranceType::GrottoGrave, COLOSSUS_GROTTO, DESERT_COLOSSUS, 0x0800}},
{{EntranceType::GrottoGrave, LAKE_HYLIA, LH_GROTTO, 0x0701},
{EntranceType::GrottoGrave, LH_GROTTO, LAKE_HYLIA, 0x0801}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_STORMS_GROTTO, 0x0702},
{EntranceType::GrottoGrave, ZR_STORMS_GROTTO, ZORAS_RIVER, 0x0802}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_FAIRY_GROTTO, 0x0703},
{EntranceType::GrottoGrave, ZR_FAIRY_GROTTO, ZORAS_RIVER, 0x0803}},
{{EntranceType::GrottoGrave, ZORAS_RIVER, ZR_OPEN_GROTTO, 0x0704},
{EntranceType::GrottoGrave, ZR_OPEN_GROTTO, ZORAS_RIVER, 0x0804}},
{{EntranceType::GrottoGrave, DMC_LOWER_NEARBY, DMC_HAMMER_GROTTO, 0x0705},
{EntranceType::GrottoGrave, DMC_HAMMER_GROTTO, DMC_LOWER_LOCAL, 0x0805}},
{{EntranceType::GrottoGrave, DMC_UPPER_NEARBY, DMC_UPPER_GROTTO, 0x0706},
{EntranceType::GrottoGrave, DMC_UPPER_GROTTO, DMC_UPPER_LOCAL, 0x0806}},
{{EntranceType::GrottoGrave, GC_GROTTO_PLATFORM, GC_GROTTO, 0x0707},
{EntranceType::GrottoGrave, GC_GROTTO, GC_GROTTO_PLATFORM, 0x0807}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_TRAIL, DMT_STORMS_GROTTO, 0x0708},
{EntranceType::GrottoGrave, DMT_STORMS_GROTTO, DEATH_MOUNTAIN_TRAIL, 0x0808}},
{{EntranceType::GrottoGrave, DEATH_MOUNTAIN_SUMMIT, DMT_COW_GROTTO, 0x0709},
{EntranceType::GrottoGrave, DMT_COW_GROTTO, DEATH_MOUNTAIN_SUMMIT, 0x0809}},
{{EntranceType::GrottoGrave, KAK_BACKYARD, KAK_OPEN_GROTTO, 0x070A},
{EntranceType::GrottoGrave, KAK_OPEN_GROTTO, KAK_BACKYARD, 0x080A}},
{{EntranceType::GrottoGrave, KAKARIKO_VILLAGE, KAK_REDEAD_GROTTO, 0x070B},
{EntranceType::GrottoGrave, KAK_REDEAD_GROTTO, KAKARIKO_VILLAGE, 0x080B}},
{{EntranceType::GrottoGrave, HYRULE_CASTLE_GROUNDS, HC_STORMS_GROTTO, 0x070C},
{EntranceType::GrottoGrave, HC_STORMS_GROTTO, CASTLE_GROUNDS, 0x080C}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_TEKTITE_GROTTO, 0x070D},
{EntranceType::GrottoGrave, HF_TEKTITE_GROTTO, HYRULE_FIELD, 0x080D}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_KAK_GROTTO, 0x070E},
{EntranceType::GrottoGrave, HF_NEAR_KAK_GROTTO, HYRULE_FIELD, 0x080E}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_FAIRY_GROTTO, 0x070F},
{EntranceType::GrottoGrave, HF_FAIRY_GROTTO, HYRULE_FIELD, 0x080F}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_NEAR_MARKET_GROTTO, 0x0710},
{EntranceType::GrottoGrave, HF_NEAR_MARKET_GROTTO, HYRULE_FIELD, 0x0810}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_COW_GROTTO, 0x0711},
{EntranceType::GrottoGrave, HF_COW_GROTTO, HYRULE_FIELD, 0x0811}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_INSIDE_FENCE_GROTTO, 0x0712},
{EntranceType::GrottoGrave, HF_INSIDE_FENCE_GROTTO, HYRULE_FIELD, 0x0812}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_OPEN_GROTTO, 0x0713},
{EntranceType::GrottoGrave, HF_OPEN_GROTTO, HYRULE_FIELD, 0x0813}},
{{EntranceType::GrottoGrave, HYRULE_FIELD, HF_SOUTHEAST_GROTTO, 0x0714},
{EntranceType::GrottoGrave, HF_SOUTHEAST_GROTTO, HYRULE_FIELD, 0x0814}},
{{EntranceType::GrottoGrave, LON_LON_RANCH, LLR_GROTTO, 0x0715},
{EntranceType::GrottoGrave, LLR_GROTTO, LON_LON_RANCH, 0x0815}},
{{EntranceType::GrottoGrave, SFM_ENTRYWAY, SFM_WOLFOS_GROTTO, 0x0716},
{EntranceType::GrottoGrave, SFM_WOLFOS_GROTTO, SFM_ENTRYWAY, 0x0816}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_STORMS_GROTTO, 0x0717},
{EntranceType::GrottoGrave, SFM_STORMS_GROTTO, SACRED_FOREST_MEADOW, 0x0817}},
{{EntranceType::GrottoGrave, SACRED_FOREST_MEADOW, SFM_FAIRY_GROTTO, 0x0718},
{EntranceType::GrottoGrave, SFM_FAIRY_GROTTO, SACRED_FOREST_MEADOW, 0x0818}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, LW_SCRUBS_GROTTO, 0x0719},
{EntranceType::GrottoGrave, LW_SCRUBS_GROTTO, LW_BEYOND_MIDO, 0x0819}},
{{EntranceType::GrottoGrave, THE_LOST_WOODS, LW_NEAR_SHORTCUTS_GROTTO, 0x071A},
{EntranceType::GrottoGrave, LW_NEAR_SHORTCUTS_GROTTO, THE_LOST_WOODS, 0x081A}},
{{EntranceType::GrottoGrave, KOKIRI_FOREST, KF_STORMS_GROTTO, 0x071B},
{EntranceType::GrottoGrave, KF_STORMS_GROTTO, KOKIRI_FOREST, 0x081B}},
{{EntranceType::GrottoGrave, ZORAS_DOMAIN, ZD_STORMS_GROTTO, 0x071C},
{EntranceType::GrottoGrave, ZD_STORMS_GROTTO, ZORAS_DOMAIN, 0x081C}},
{{EntranceType::GrottoGrave, GERUDO_FORTRESS, GF_STORMS_GROTTO, 0x071D},
{EntranceType::GrottoGrave, GF_STORMS_GROTTO, GERUDO_FORTRESS, 0x081D}},
{{EntranceType::GrottoGrave, GV_FORTRESS_SIDE, GV_STORMS_GROTTO, 0x071E},
{EntranceType::GrottoGrave, GV_STORMS_GROTTO, GV_FORTRESS_SIDE, 0x081E}},
{{EntranceType::GrottoGrave, GV_GROTTO_LEDGE, GV_OCTOROK_GROTTO, 0x071F},
{EntranceType::GrottoGrave, GV_OCTOROK_GROTTO, GV_GROTTO_LEDGE, 0x081F}},
{{EntranceType::GrottoGrave, LW_BEYOND_MIDO, DEKU_THEATER, 0x0720},
{EntranceType::GrottoGrave, DEKU_THEATER, LW_BEYOND_MIDO, 0x0820}},
// Graves have their own specified entrance indices
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_SHIELD_GRAVE, 0x004B},
{EntranceType::GrottoGrave, GRAVEYARD_SHIELD_GRAVE, THE_GRAVEYARD, 0x035D}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_HEART_PIECE_GRAVE, 0x031C},
{EntranceType::GrottoGrave, GRAVEYARD_HEART_PIECE_GRAVE, THE_GRAVEYARD, 0x0361}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_COMPOSERS_GRAVE, 0x002D},
{EntranceType::GrottoGrave, GRAVEYARD_COMPOSERS_GRAVE, THE_GRAVEYARD, 0x050B}},
{{EntranceType::GrottoGrave, THE_GRAVEYARD, GRAVEYARD_DAMPES_GRAVE, 0x044F},
{EntranceType::GrottoGrave, GRAVEYARD_DAMPES_GRAVE, THE_GRAVEYARD, 0x0359}},
{{EntranceType::Overworld, KOKIRI_FOREST, LW_BRIDGE_FROM_FOREST, 0x05E0},
{EntranceType::Overworld, LW_BRIDGE, KOKIRI_FOREST, 0x020D}},
{{EntranceType::Overworld, KOKIRI_FOREST, THE_LOST_WOODS, 0x011E},
{EntranceType::Overworld, LW_FOREST_EXIT, KOKIRI_FOREST, 0x0286}},
{{EntranceType::Overworld, THE_LOST_WOODS, GC_WOODS_WARP, 0x04E2},
{EntranceType::Overworld, GC_WOODS_WARP, THE_LOST_WOODS, 0x04D6}},
{{EntranceType::Overworld, THE_LOST_WOODS, ZORAS_RIVER, 0x01DD},
{EntranceType::Overworld, ZORAS_RIVER, THE_LOST_WOODS, 0x04DA}},
{{EntranceType::Overworld, LW_BEYOND_MIDO, SFM_ENTRYWAY, 0x00FC},
{EntranceType::Overworld, SFM_ENTRYWAY, LW_BEYOND_MIDO, 0x01A9}},
{{EntranceType::Overworld, LW_BRIDGE, HYRULE_FIELD, 0x0185},
{EntranceType::Overworld, HYRULE_FIELD, LW_BRIDGE, 0x04DE}},
{{EntranceType::Overworld, HYRULE_FIELD, LAKE_HYLIA, 0x0102},
{EntranceType::Overworld, LAKE_HYLIA, HYRULE_FIELD, 0x0189}},
{{EntranceType::Overworld, HYRULE_FIELD, GERUDO_VALLEY, 0x0117},
{EntranceType::Overworld, GERUDO_VALLEY, HYRULE_FIELD, 0x018D}},
{{EntranceType::Overworld, HYRULE_FIELD, MARKET_ENTRANCE, 0x0276},
{EntranceType::Overworld, MARKET_ENTRANCE, HYRULE_FIELD, 0x01FD}},
{{EntranceType::Overworld, HYRULE_FIELD, KAKARIKO_VILLAGE, 0x00DB},
{EntranceType::Overworld, KAKARIKO_VILLAGE, HYRULE_FIELD, 0x017D}},
{{EntranceType::Overworld, HYRULE_FIELD, ZR_FRONT, 0x00EA},
{EntranceType::Overworld, ZR_FRONT, HYRULE_FIELD, 0x0181}},
{{EntranceType::Overworld, HYRULE_FIELD, LON_LON_RANCH, 0x0157},
{EntranceType::Overworld, LON_LON_RANCH, HYRULE_FIELD, 0x01F9}},
{{EntranceType::Overworld, LAKE_HYLIA, ZORAS_DOMAIN, 0x0328},
{EntranceType::Overworld, ZORAS_DOMAIN, LAKE_HYLIA, 0x0560}},
{{EntranceType::Overworld, GV_FORTRESS_SIDE, GERUDO_FORTRESS, 0x0129},
{EntranceType::Overworld, GERUDO_FORTRESS, GV_FORTRESS_SIDE, 0x022D}},
{{EntranceType::Overworld, GF_OUTSIDE_GATE, WASTELAND_NEAR_FORTRESS, 0x0130},
{EntranceType::Overworld, WASTELAND_NEAR_FORTRESS, GF_OUTSIDE_GATE, 0x03AC}},
{{EntranceType::Overworld, WASTELAND_NEAR_COLOSSUS, DESERT_COLOSSUS, 0x0123},
{EntranceType::Overworld, DESERT_COLOSSUS, WASTELAND_NEAR_COLOSSUS, 0x0365}},
{{EntranceType::Overworld, MARKET_ENTRANCE, THE_MARKET, 0x00B1},
{EntranceType::Overworld, THE_MARKET, MARKET_ENTRANCE, 0x0033}},
{{EntranceType::Overworld, THE_MARKET, CASTLE_GROUNDS, 0x0138},
{EntranceType::Overworld, CASTLE_GROUNDS, THE_MARKET, 0x025A}},
{{EntranceType::Overworld, THE_MARKET, TOT_ENTRANCE, 0x0171},
{EntranceType::Overworld, TOT_ENTRANCE, THE_MARKET, 0x025E}},
{{EntranceType::Overworld, KAKARIKO_VILLAGE, THE_GRAVEYARD, 0x00E4},
{EntranceType::Overworld, THE_GRAVEYARD, KAKARIKO_VILLAGE, 0x0195}},
{{EntranceType::Overworld, KAK_BEHIND_GATE, DEATH_MOUNTAIN_TRAIL, 0x013D},
{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, KAK_BEHIND_GATE, 0x0191}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_TRAIL, GORON_CITY, 0x014D},
{EntranceType::Overworld, GORON_CITY, DEATH_MOUNTAIN_TRAIL, 0x01B9}},
{{EntranceType::Overworld, GC_DARUNIAS_CHAMBER, DMC_LOWER_LOCAL, 0x0246},
{EntranceType::Overworld, DMC_LOWER_NEARBY, GC_DARUNIAS_CHAMBER, 0x01C1}},
{{EntranceType::Overworld, DEATH_MOUNTAIN_SUMMIT, DMC_UPPER_LOCAL, 0x0147},
{EntranceType::Overworld, DMC_UPPER_NEARBY, DEATH_MOUNTAIN_SUMMIT, 0x01BD}},
{{EntranceType::Overworld, ZR_BEHIND_WATERFALL, ZORAS_DOMAIN, 0x0108},
{EntranceType::Overworld, ZORAS_DOMAIN, ZR_BEHIND_WATERFALL, 0x019D}},
{{EntranceType::Overworld, ZD_BEHIND_KING_ZORA, ZORAS_FOUNTAIN, 0x0225},
{EntranceType::Overworld, ZORAS_FOUNTAIN, ZD_BEHIND_KING_ZORA, 0x01A1}},
};
entranceShuffleFailure = false;
SetAllEntrancesData(entranceShuffleTable);
// one_way_entrance_pools = OrderedDict()
std::map<EntranceType, std::vector<Entrance*>> entrancePools = {};
// one_way_priorities = {}
//owl drops
//spawns
//warpsongs
//Shuffle Dungeon Entrances
if (Settings::ShuffleDungeonEntrances.IsNot(SHUFFLEDUNGEONS_OFF)) {
entrancePools[EntranceType::Dungeon] = GetShuffleableEntrances(EntranceType::Dungeon);
//Add Ganon's Castle, if set to On + Ganon
if (Settings::ShuffleDungeonEntrances.Is(SHUFFLEDUNGEONS_GANON)) {
AddElementsToPool(entrancePools[EntranceType::Dungeon], GetShuffleableEntrances(EntranceType::GanonDungeon));
}
//If forest is closed don't allow a forest escape via spirit temple hands
if (Settings::OpenForest.Is(OPENFOREST_CLOSED) && !(Settings::ShuffleOverworldEntrances || Settings::ShuffleInteriorEntrances)) {
FilterAndEraseFromPool(entrancePools[EntranceType::Dungeon], [](const Entrance* entrance){return entrance->GetParentRegionKey() == KF_OUTSIDE_DEKU_TREE &&
entrance->GetConnectedRegionKey() == DEKU_TREE_ENTRYWAY;});
}
//decoupled entrances stuff
}
//interior entrances
if (Settings::ShuffleInteriorEntrances.IsNot(SHUFFLEINTERIORS_OFF)) {
entrancePools[EntranceType::Interior] = GetShuffleableEntrances(EntranceType::Interior);
//special interiors
if (Settings::ShuffleInteriorEntrances.Is(SHUFFLEINTERIORS_ALL)) {
AddElementsToPool(entrancePools[EntranceType::Interior], GetShuffleableEntrances(EntranceType::SpecialInterior));
}
//decoupled entrance stuff
}
//grotto entrances
if (Settings::ShuffleGrottoEntrances) {
entrancePools[EntranceType::GrottoGrave] = GetShuffleableEntrances(EntranceType::GrottoGrave);
//decoupled entrance stuff
}
//overworld entrances
if (Settings::ShuffleOverworldEntrances) {
bool excludeOverworldReverse = false; //mix_entrance_pools == all && !decouple_entrances
entrancePools[EntranceType::Overworld] = GetShuffleableEntrances(EntranceType::Overworld, excludeOverworldReverse);
// if not worlds[0].decouple_entrances:
// entrance_pools['Overworld'].remove(world.get_entrance('GV Lower Stream -> Lake Hylia'))
if (!excludeOverworldReverse) {
totalRandomizableEntrances -= 26; //Only count each overworld entrance once
}
}
//Set shuffled entrances as such
for (auto& pool : entrancePools) {
for (Entrance* entrance : pool.second) {
entrance->SetAsShuffled();
totalRandomizableEntrances++;
if (entrance->GetReverse() != nullptr) {
entrance->GetReverse()->SetAsShuffled();
}
}
}
//combine entrance pools if mixing pools
//Build target entrance pools and set the assumption for entrances being reachable
//one way entrance stuff
//assume entrance pools for each type
std::map<EntranceType, std::vector<Entrance*>> targetEntrancePools = {};
for (auto& pool : entrancePools) {
targetEntrancePools[pool.first] = AssumeEntrancePool(pool.second);
}
//distribution stuff
//check placed on-way entrances
//remove replaced entrances so we don't place two in one target
//remvoe priority targets if any placed entrances point at their regions
//place priority entrances
//delete all targets that we just placed from one way target pools so multiple one way entrances don't use the same target
//shuffle all entrances among pools to shuffle
for (auto& pool : entrancePools) {
ShuffleEntrancePool(pool.second, targetEntrancePools[pool.first]);
if (entranceShuffleFailure) {
return ENTRANCE_SHUFFLE_FAILURE;
}
}
return ENTRANCE_SHUFFLE_SUCCESS;
}
//Create the set of entrances that will be overridden
void CreateEntranceOverrides() {
entranceOverrides.clear();
if (noRandomEntrances) {
return;
}
SPDLOG_INFO("\nCREATING ENTRANCE OVERRIDES\n");
auto allShuffleableEntrances = GetShuffleableEntrances(EntranceType::All, false);
for (Entrance* entrance : allShuffleableEntrances) {
//Double-check to make sure the entrance is actually shuffled
if (!entrance->IsShuffled()) {
continue;
}
auto message = "Setting " + entrance->to_string() + "\n";
SPDLOG_INFO(message);
int16_t originalIndex = entrance->GetIndex();
int16_t destinationIndex = entrance->GetReverse()->GetIndex();
int16_t originalBlueWarp = entrance->GetBlueWarp();
int16_t replacementIndex = entrance->GetReplacement()->GetIndex();
int16_t replacementDestinationIndex = entrance->GetReplacement()->GetReverse()->GetIndex();
entranceOverrides.push_back({
.index = originalIndex,
.destination = destinationIndex,
.blueWarp = originalBlueWarp,
.override = replacementIndex,
.overrideDestination = replacementDestinationIndex,
});
message = "\tOriginal: " + std::to_string(originalIndex) + "\n";
SPDLOG_INFO(message);
message = "\tReplacement " + std::to_string(replacementIndex) + "\n";
SPDLOG_INFO(message);
}
}
std::vector<std::list<Entrance*>> playthroughEntrances;

View File

@ -0,0 +1,320 @@
#pragma once
#include "keys.hpp"
#include "location_access.hpp"
#include "debug.hpp"
#include <string>
#include <list>
#define ENTRANCE_SHUFFLE_SUCCESS 0
#define ENTRANCE_SHUFFLE_FAILURE 1
typedef struct {
int16_t index;
int16_t destination;
int16_t blueWarp;
int16_t override;
int16_t overrideDestination;
} EntranceOverride;
typedef enum {
ENTRANCE_GROUP_NO_GROUP,
ENTRANCE_GROUP_KOKIRI_FOREST,
ENTRANCE_GROUP_LOST_WOODS,
ENTRANCE_GROUP_KAKARIKO,
ENTRANCE_GROUP_GRAVEYARD,
ENTRANCE_GROUP_DEATH_MOUNTAIN_TRAIL,
ENTRANCE_GROUP_DEATH_MOUNTAIN_CRATER,
ENTRANCE_GROUP_GORON_CITY,
ENTRANCE_GROUP_ZORAS_DOMAIN,
ENTRANCE_GROUP_HYRULE_FIELD,
ENTRANCE_GROUP_LON_LON_RANCH,
ENTRANCE_GROUP_LAKE_HYLIA,
ENTRANCE_GROUP_GERUDO_VALLEY,
ENTRANCE_GROUP_HAUNTED_WASTELAND,
ENTRANCE_GROUP_MARKET,
ENTRANCE_GROUP_HYRULE_CASTLE,
SPOILER_ENTRANCE_GROUP_COUNT,
} SpoilerEntranceGroup;
typedef enum {
ENTRANCE_TYPE_OVERWORLD,
ENTRANCE_TYPE_INTERIOR,
ENTRANCE_TYPE_GROTTO,
ENTRANCE_TYPE_DUNGEON,
ENTRANCE_TYPE_COUNT,
} TrackerEntranceType;
typedef struct {
int16_t index;
char* name;
SpoilerEntranceGroup group;
TrackerEntranceType type;
uint8_t oneExit;
} EntranceData;
typedef struct {
uint8_t EntranceCount;
uint16_t GroupEntranceCounts[SPOILER_ENTRANCE_GROUP_COUNT];
uint16_t GroupOffsets[SPOILER_ENTRANCE_GROUP_COUNT];
} EntranceTrackingData;
extern std::list<EntranceOverride> entranceOverrides;
enum class EntranceType {
None,
OwlDrop,
Spawn,
WarpSong,
Dungeon,
GanonDungeon,
Interior,
SpecialInterior,
GrottoGrave,
Overworld,
Extra,
All,
};
class Entrance {
public:
Entrance(uint32_t connectedRegion_, std::vector<ConditionFn> conditions_met_)
: connectedRegion(connectedRegion_) {
conditions_met.resize(2);
for (size_t i = 0; i < conditions_met_.size(); i++) {
conditions_met[i] = conditions_met_[i];
}
}
bool GetConditionsMet() const {
if (Settings::Logic.Is(LOGIC_NONE) || Settings::Logic.Is(LOGIC_VANILLA)) {
return true;
} else if (Settings::Logic.Is(LOGIC_GLITCHLESS)) {
return conditions_met[0]();
} else if (Settings::Logic.Is(LOGIC_GLITCHED)) {
if (conditions_met[0]()) {
return true;
} else if (conditions_met[1] != NULL) {
return conditions_met[1]();
}
}
return false;
}
std::string to_string() const {
return AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName;
}
void SetName(std::string name_ = "") {
if (name_ == "") {
name = AreaTable(parentRegion)->regionName + " -> " + AreaTable(connectedRegion)->regionName;
} else {
name = std::move(name_);
}
}
std::string GetName() const {
return name;
}
void printAgeTimeAccess() {
CitraPrint("Name: ");
CitraPrint(name);
auto message = "Child Day: " + std::to_string(CheckConditionAtAgeTime(Logic::IsChild, Logic::AtDay)) + "\t"
"Child Night: " + std::to_string(CheckConditionAtAgeTime(Logic::IsChild, Logic::AtNight)) + "\t"
"Adult Day: " + std::to_string(CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtDay)) + "\t"
"Adult Night: " + std::to_string(CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtNight));
CitraPrint(message);
}
bool ConditionsMet(bool allAgeTimes = false) const {
Area* parent = AreaTable(parentRegion);
int conditionsMet = 0;
if (allAgeTimes && !parent->AllAccess()) {
return false;
}
//check all possible day/night condition combinations
conditionsMet = (parent->childDay && CheckConditionAtAgeTime(Logic::IsChild, Logic::AtDay, allAgeTimes)) +
(parent->childNight && CheckConditionAtAgeTime(Logic::IsChild, Logic::AtNight, allAgeTimes)) +
(parent->adultDay && CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtDay, allAgeTimes)) +
(parent->adultNight && CheckConditionAtAgeTime(Logic::IsAdult, Logic::AtNight, allAgeTimes));
return conditionsMet && (!allAgeTimes || conditionsMet == 4);
}
uint32_t Getuint32_t() const {
return connectedRegion;
}
//set the logic to be a specific age and time of day and see if the condition still holds
bool CheckConditionAtAgeTime(bool& age, bool& time, bool passAnyway = false) const {
Logic::IsChild = false;
Logic::IsAdult = false;
Logic::AtDay = false;
Logic::AtNight = false;
time = true;
age = true;
Logic::UpdateHelpers();
return GetConditionsMet() && (connectedRegion != NONE || passAnyway);
}
uint32_t GetConnectedRegionKey() const {
return connectedRegion;
}
Area* GetConnectedRegion() const {
return AreaTable(connectedRegion);
}
void SetParentRegion(uint32_t newParent) {
parentRegion = newParent;
}
uint32_t GetParentRegionKey() const {
return parentRegion;
}
Area* GetParentRegion() const {
return AreaTable(parentRegion);
}
void SetNewEntrance(uint32_t newRegion) {
connectedRegion = newRegion;
}
void SetAsShuffled() {
shuffled = true;
}
bool IsShuffled() const {
return shuffled;
}
bool IsAddedToPool() const {
return addedToPool;
}
void AddToPool() {
addedToPool = true;
}
void RemoveFromPool() {
addedToPool = false;
}
void SetAsPrimary() {
primary = true;
}
bool IsPrimary() const {
return primary;
}
int16_t GetIndex() const {
return index;
}
void SetIndex(int16_t newIndex) {
index = newIndex;
}
int16_t GetBlueWarp() const {
return blueWarp;
}
void SetBlueWarp(int16_t newBlueWarp) {
blueWarp = newBlueWarp;
}
Entrance* GetAssumed() const {
return assumed;
}
void SetReplacement(Entrance* newReplacement) {
replacement = newReplacement;
}
Entrance* GetReplacement() const {
return replacement;
}
EntranceType GetType() const {
return type;
}
void SetType(EntranceType newType) {
type = newType;
}
Entrance* GetReverse() const {
return reverse;
}
void Connect(uint32_t newConnectedRegion) {
connectedRegion = newConnectedRegion;
AreaTable(newConnectedRegion)->entrances.push_front(this);
}
uint32_t Disconnect() {
AreaTable(connectedRegion)->entrances.remove_if([this](const auto entrance){return this == entrance;});
uint32_t previouslyConnected = connectedRegion;
connectedRegion = NONE;
return previouslyConnected;
}
void BindTwoWay(Entrance* otherEntrance) {
reverse = otherEntrance;
otherEntrance->reverse = this;
}
Entrance* GetNewTarget() {
AreaTable(ROOT)->AddExit(ROOT, connectedRegion, []{return true;});
Entrance* targetEntrance = AreaTable(ROOT)->GetExit(connectedRegion);
targetEntrance->SetReplacement(this);
targetEntrance->SetName(GetParentRegion()->regionName + " -> " + GetConnectedRegion()->regionName);
return targetEntrance;
}
Entrance* AssumeReachable() {
if (assumed == nullptr) {
assumed = GetNewTarget();
Disconnect();
}
return assumed;
}
private:
uint32_t parentRegion;
uint32_t connectedRegion;
std::vector<ConditionFn> conditions_met;
//Entrance Randomizer stuff
EntranceType type = EntranceType::None;
Entrance* target = nullptr;
Entrance* reverse = nullptr;
Entrance* assumed = nullptr;
Entrance* replacement = nullptr;
int16_t index = 0xFFFF;
int16_t blueWarp = 0;
bool shuffled = false;
bool primary = false;
bool addedToPool = false;
std::string name = "";
};
int ShuffleAllEntrances();
void CreateEntranceOverrides();
EntranceTrackingData* GetEntranceTrackingData();
extern std::vector<std::list<Entrance*>> playthroughEntrances;
extern bool noRandomEntrances;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
#pragma once
#include "keys.hpp"
#include <vector>
#include <string>
enum class SearchMode {
ReachabilitySearch,
GeneratePlaythrough,
CheckBeatable,
AllLocationsReachable,
ValidateWorld,
TimePassAccess,
TempleOfTimeAccess,
ValidStartingRegion,
PoeCollectorAccess,
};
void ClearProgress();
void VanillaFill();
int Fill();
std::vector<uint32_t> GetAccessibleLocations(const std::vector<uint32_t>& allowedLocations,
SearchMode mode = SearchMode::ReachabilitySearch, std::string ignore = "",
bool checkPoeCollectorAccess = false,
bool checkOtherEntranceAccess = false);

Some files were not shown because too many files have changed in this diff Show More