Rando: Skeleton key (#3997)

* Initial Implementation

* Add temporary model to the skeleton key
This commit is contained in:
Pepe20129 2024-07-17 18:55:05 +02:00 committed by GitHub
parent 7595a5a65e
commit 8b6c183776
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 194 additions and 81 deletions

View File

@ -596,6 +596,8 @@ const std::vector<FlagTable> flagTables = {
{ RAND_INF_ZD_FISH_4, "RAND_INF_ZD_FISH_4" },
{ RAND_INF_ZD_FISH_5, "RAND_INF_ZD_FISH_5" },
{ RAND_INF_HAS_SKELETON_KEY, "RAND_INF_HAS_SKELETON_KEY" },
{ RAND_INF_LINKS_POCKET, "RAND_INF_LINKS_POCKET" },
{ RAND_INF_LEARNED_EPONA_SONG, "RAND_INF_LEARNED_EPONA_SONG" },
{ RAND_INF_DARUNIAS_JOY, "RAND_INF_DARUNIAS_JOY" },

View File

@ -29,8 +29,10 @@
#include "src/overlays/actors/ovl_En_Tp/z_en_tp.h"
#include "src/overlays/actors/ovl_En_Firefly/z_en_firefly.h"
#include "src/overlays/actors/ovl_En_Xc/z_en_xc.h"
#include "src/overlays//actors/ovl_Fishing/z_fishing.h"
#include "src/overlays/actors/ovl_Fishing/z_fishing.h"
#include "src/overlays/actors/ovl_Obj_Switch/z_obj_switch.h"
#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h"
#include "src/overlays/actors/ovl_En_Door/z_en_door.h"
#include "objects/object_link_boy/object_link_boy.h"
#include "objects/object_link_child/object_link_child.h"
@ -1712,6 +1714,24 @@ void RegisterRandomizerCompasses() {
});
}
void RegisterSkeletonKey() {
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorUpdate>([](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
if (Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY)) {
if (actor->id == ACTOR_EN_DOOR) {
EnDoor* door = (EnDoor*)actor;
door->lockTimer = 0;
} else if (actor->id == ACTOR_DOOR_SHUTTER) {
DoorShutter* shutterDoor = (DoorShutter*)actor;
if (shutterDoor->doorType == SHUTTER_KEY_LOCKED) {
shutterDoor->unk_16E = 0;
}
}
}
});
}
void InitMods() {
RandomizerRegisterHooks();
TimeSaverRegisterHooks();
@ -1760,4 +1780,5 @@ void InitMods() {
RegisterPatchHandHandler();
RegisterHurtContainerModeHandler();
RegisterPauseMenuHooks();
RegisterSkeletonKey();
}

View File

@ -782,6 +782,10 @@ void GenerateItemPool() {
ctx->possibleIceTrapModels.push_back(RG_OCARINA_C_RIGHT_BUTTON);
}
if (ctx->GetOption(RSK_SKELETON_KEY)) {
AddItemToMainPool(RG_SKELETON_KEY);
}
if (ctx->GetOption(RSK_SHUFFLE_SWIM)) {
AddItemToMainPool(RG_PROGRESSIVE_SCALE);
}

View File

@ -543,3 +543,53 @@ extern "C" void Randomizer_DrawFishingPoleGI(PlayState* play, GetItemEntry* getI
CLOSE_DISPS(play->state.gfxCtx);
}
int skeletonKeyHue = 0;
// Runs every frame to update rainbow hue, taken from CosmeticsEditor.cpp.
Color_RGBA8 GetSkeletonKeyColor() {
float rainbowSpeed = 0.6f;
float frequency = 2 * M_PI / (360 * rainbowSpeed);
Color_RGBA8 color;
color.r = sin(frequency * skeletonKeyHue + 0) * 127 + 128;
color.g = sin(frequency * skeletonKeyHue + (2 * M_PI / 3)) * 127 + 128;
color.b = sin(frequency * skeletonKeyHue + (4 * M_PI / 3)) * 127 + 128;
color.a = 255;
skeletonKeyHue++;
if (skeletonKeyHue >= (360 * rainbowSpeed)) skeletonKeyHue = 0;
return color;
}
int test = 0;
extern "C" void Randomizer_DrawSkeletonKey(PlayState* play, GetItemEntry* getItemEntry) {
OPEN_DISPS(play->state.gfxCtx);
Color_RGBA8 color = GetSkeletonKeyColor();
Gfx_SetupDL_25Opa(play->state.gfxCtx);
test += 1;
if (test > 40) {
test -= 80;
}
Matrix_RotateZ(M_PI / 40 * test, MTXMODE_APPLY);
Matrix_RotateY(M_PI / 40 * test, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__),
G_MTX_MODELVIEW | G_MTX_LOAD);
gDPSetGrayscaleColor(POLY_OPA_DISP++, color.r, color.g, color.b, color.a);
gSPGrayscale(POLY_OPA_DISP++, true);
gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gGiSmallKeyDL);
gSPGrayscale(POLY_OPA_DISP++, false);
CLOSE_DISPS(play->state.gfxCtx);
}

View File

@ -21,6 +21,7 @@ void Randomizer_DrawTriforcePieceGI(PlayState* play, GetItemEntry getItemEntry);
void Randomizer_DrawOcarinaButton(PlayState* play, GetItemEntry* getItemEntry);
void Randomizer_DrawBronzeScale(PlayState* play, GetItemEntry* getItemEntry);
void Randomizer_DrawFishingPoleGI(PlayState* play, GetItemEntry* getItemEntry);
void Randomizer_DrawSkeletonKey(PlayState* play, GetItemEntry* getItemEntry);
void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry getItemEntry);
#define GET_ITEM_MYSTERY \

View File

@ -289,6 +289,9 @@ void Rando::StaticData::InitItemTable() {
itemTable[RG_BRONZE_SCALE] = Item(RG_BRONZE_SCALE, Text{ "Bronze Scale", "!!!", "!!!" }, ITEMTYPE_ITEM, GI_SCALE_SILVER, true, &logic->ProgressiveWallet, RHT_BRONZE_SCALE, RG_BRONZE_SCALE, OBJECT_GI_SCALE, GID_SCALE_SILVER, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_BRONZE_SCALE].SetCustomDrawFunc(Randomizer_DrawBronzeScale);
itemTable[RG_SKELETON_KEY] = Item(RG_SKELETON_KEY, Text{ "Skeleton Key", "!!!", "!!!" }, ITEMTYPE_ITEM, GI_STONE_OF_AGONY, true, &logic->SkeletonKey, RHT_SKELETON_KEY, RG_SKELETON_KEY, OBJECT_GI_MAP, GID_STONE_OF_AGONY, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_SKELETON_KEY].SetCustomDrawFunc(Randomizer_DrawSkeletonKey);
itemTable[RG_DEKU_STICK_BAG] = Item(RG_DEKU_STICK_BAG, Text{ "Deku Stick Bag", "!!!", "!!!" }, ITEMTYPE_ITEM, GI_STICK_UPGRADE_30, true, &logic->ProgressiveStickBag, RHT_NONE, RG_DEKU_STICK_BAG, OBJECT_GI_STICK, GID_STICK, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);
itemTable[RG_DEKU_NUT_BAG] = Item(RG_DEKU_NUT_BAG, Text{ "Deku Nut Bag", "!!!", "!!!" }, ITEMTYPE_ITEM, GI_NUT_UPGRADE_30, true, &logic->ProgressiveNutBag, RHT_NONE, RG_DEKU_NUT_BAG, OBJECT_GI_NUTS, GID_NUTS, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER);

View File

@ -411,6 +411,9 @@ namespace Rando {
}
bool Logic::SmallKeys(RandomizerRegion dungeon, uint8_t requiredAmountGlitchless, uint8_t requiredAmountGlitched) {
if (SkeletonKey) {
return true;
}
switch (dungeon) {
case RR_FOREST_TEMPLE:
/*if (IsGlitched && (GetDifficultyValueFromString(GlitchHookshotJump_Boots) >= static_cast<uint8_t>(GlitchDifficulty::INTERMEDIATE) || GetDifficultyValueFromString(GlitchHoverBoost) >= static_cast<uint8_t>(GlitchDifficulty::NOVICE) ||
@ -673,6 +676,9 @@ namespace Rando {
//Triforce Pieces
TriforcePieces = 0;
//Skeleton Key
SkeletonKey = false;
//Boss Souls
CanSummonGohma = false;
CanSummonKingDodongo = false;

View File

@ -171,6 +171,9 @@ class Logic {
// Triforce Pieces
uint8_t TriforcePieces = 0;
// Skeleton Key
bool SkeletonKey = false;
// Boss Keys
bool BossKeyForestTemple = false;
bool BossKeyFireTemple = false;

View File

@ -542,7 +542,6 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_KAK_50_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 50 tokens will tell you the reward";
mOptionDescriptions[RSK_KAK_100_SKULLS_HINT] = "Talking to the Cursed Resident in the Skultulla House who is saved after 100 tokens will tell you the reward";
mOptionDescriptions[RSK_MASK_SHOP_HINT] = "Reading the mask shop sign will tell you rewards from showing masks at the Deku Theatre.";
mOptionDescriptions[RSK_FULL_WALLETS] = "Start with a full wallet. All wallet upgrades come filled with rupees.";
mOptionDescriptions[RSK_BOMBCHUS_IN_LOGIC] =
"Bombchus are properly considered in logic.\n"
@ -557,6 +556,7 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_BLUE_FIRE_ARROWS] =
"Ice Arrows act like Blue Fire, making them able to melt red ice. "
"Item placement logic will respect this option, so it might be required to use this to progress.";
mOptionDescriptions[RSK_SKELETON_KEY] = "Adds a new item called the \"Skeleton Key\", it unlocks every dungeon door locked by a small key.";
mOptionDescriptions[RSK_SUNLIGHT_ARROWS] =
"Light Arrows can be used to light up the sun switches instead of using the Mirror Shield. "
"Item placement logic will respect this option, so it might be required to use this to progress.";

View File

@ -2954,7 +2954,7 @@ CustomMessage Randomizer::GetGoronMessage(u16 index) {
void Randomizer::CreateCustomMessages() {
// RANDTODO: Translate into french and german and replace GIMESSAGE_UNTRANSLATED
// with GIMESSAGE(getItemID, itemID, english, german, french).
const std::array<GetItemMessage, 76> getItemMessages = {{
const std::array<GetItemMessage, 77> getItemMessages = {{
GIMESSAGE(RG_GREG_RUPEE, ITEM_MASK_GORON,
"You found %gGreg%w!",
"%gGreg%w! Du hast ihn wirklich gefunden!",
@ -3226,6 +3226,7 @@ void Randomizer::CreateCustomMessages() {
"Vous trouvez la %rtouche %y\xa6%r de&l'Ocarina%w! Vous pouvez&maintenant l'utiliser lorsque&vous en jouez!"),
GIMESSAGE_UNTRANSLATED(RG_BRONZE_SCALE, ITEM_SCALE_SILVER, "You got the %rBronze Scale%w!&The power of buoyancy is yours!"),
GIMESSAGE_UNTRANSLATED(RG_FISHING_POLE, ITEM_FISHING_POLE, "You found a lost %rFishing Pole%w!&Time to hit the pond!"),
GIMESSAGE_UNTRANSLATED(RG_SKELETON_KEY, ITEM_KEY_SMALL, "You found the %rSkeleton Key%w!"),
GIMESSAGE_UNTRANSLATED(RG_DEKU_STICK_BAG, ITEM_STICK, "You found the %rDeku Stick Bag%w!&You can now hold deku sticks!"),
GIMESSAGE_UNTRANSLATED(RG_DEKU_NUT_BAG, ITEM_NUT, "You found the %rDeku Nut Bag%w!&You can now hold deku nuts!"),
}};

View File

@ -1979,6 +1979,7 @@ typedef enum {
RG_OCARINA_C_DOWN_BUTTON,
RG_OCARINA_C_LEFT_BUTTON,
RG_OCARINA_C_RIGHT_BUTTON,
RG_SKELETON_KEY,
RG_FISHING_POLE,
RG_DEKU_STICK_BAG,
RG_DEKU_NUT_BAG,
@ -3244,6 +3245,7 @@ typedef enum {
RHT_OCARINA_C_RIGHT_BUTTON,
RHT_BRONZE_SCALE,
RHT_FISHING_POLE,
RHT_SKELETON_KEY,
RHT_EPONA,
RHT_HINT_MYSTERIOUS,
RHT_MYSTERIOUS_ITEM,
@ -3713,6 +3715,7 @@ typedef enum {
RSK_FISHSANITY_POND_COUNT,
RSK_FISHSANITY_AGE_SPLIT,
RSK_SHUFFLE_FISHING_POLE,
RSK_SKELETON_KEY,
RSK_SHUFFLE_DEKU_STICK_BAG,
RSK_SHUFFLE_DEKU_NUT_BAG,
RSK_MAX

View File

@ -257,6 +257,8 @@ typedef enum {
RAND_INF_ZD_FISH_4,
RAND_INF_ZD_FISH_5,
RAND_INF_HAS_SKELETON_KEY,
RAND_INF_LINKS_POCKET,
RAND_INF_LEARNED_EPONA_SONG,
RAND_INF_DARUNIAS_JOY,

View File

@ -184,6 +184,7 @@ void Settings::CreateOptions() {
// TODO: Compasses show rewards/woth, maps show dungeon mode
mOptions[RSK_BLUE_FIRE_ARROWS] = Option::Bool("Blue Fire Arrows", CVAR_RANDOMIZER_SETTING("BlueFireArrows"), mOptionDescriptions[RSK_BLUE_FIRE_ARROWS]);
mOptions[RSK_SUNLIGHT_ARROWS] = Option::Bool("Sunlight Arrows", CVAR_RANDOMIZER_SETTING("SunlightArrows"), mOptionDescriptions[RSK_SUNLIGHT_ARROWS]);
mOptions[RSK_SKELETON_KEY] = Option::Bool("Skeleton Key", CVAR_RANDOMIZER_SETTING("SkeletonKey"), mOptionDescriptions[RSK_SKELETON_KEY]);
mOptions[RSK_ITEM_POOL] = Option::U8("Item Pool", {"Plentiful", "Balanced", "Scarce", "Minimal"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ItemPool"), mOptionDescriptions[RSK_ITEM_POOL], WidgetType::Combobox, RO_ITEM_POOL_BALANCED);
mOptions[RSK_ICE_TRAPS] = Option::U8("Ice Traps", {"Off", "Normal", "Extra", "Mayhem", "Onslaught"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("IceTraps"), mOptionDescriptions[RSK_ICE_TRAPS], WidgetType::Combobox, RO_ICE_TRAPS_NORMAL);
// TODO: Remove Double Defense, Progressive Goron Sword
@ -764,7 +765,8 @@ void Settings::CreateOptions() {
&mOptions[RSK_BOMBCHUS_IN_LOGIC],
&mOptions[RSK_ENABLE_BOMBCHU_DROPS],
&mOptions[RSK_BLUE_FIRE_ARROWS],
&mOptions[RSK_SUNLIGHT_ARROWS]
&mOptions[RSK_SUNLIGHT_ARROWS],
&mOptions[RSK_SKELETON_KEY],
}, false, WidgetContainerType::COLUMN);
mOptionGroups[RSG_GAMEPLAY_IMGUI_TABLE] = OptionGroup::SubGroup("Gameplay", {
&mOptionGroups[RSG_TIMESAVERS_IMGUI],
@ -992,6 +994,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_DAMAGE_MULTIPLIER],
&mOptions[RSK_BLUE_FIRE_ARROWS],
&mOptions[RSK_SUNLIGHT_ARROWS],
&mOptions[RSK_SKELETON_KEY],
});
mOptionGroups[RSG_ITEM_POOL] = OptionGroup("Item Pool Settings", std::initializer_list<Option*>({
&mOptions[RSK_ITEM_POOL],
@ -1210,6 +1213,7 @@ void Settings::CreateOptions() {
{ "Miscellaneous Settings:Hint Distribution", RSK_HINT_DISTRIBUTION },
{ "Miscellaneous Settings:Blue Fire Arrows", RSK_BLUE_FIRE_ARROWS },
{ "Miscellaneous Settings:Sunlight Arrows", RSK_SUNLIGHT_ARROWS },
{ "Miscellaneous Settings:Skeleton Key", RSK_SKELETON_KEY },
{ "Timesaver Settings:Skip Child Zelda", RSK_SKIP_CHILD_ZELDA },
{ "Start with Consumables", RSK_STARTING_CONSUMABLES },
{ "Full Wallets", RSK_FULL_WALLETS },
@ -2359,6 +2363,7 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
case RSK_SKULLS_SUNS_SONG:
case RSK_BLUE_FIRE_ARROWS:
case RSK_SUNLIGHT_ARROWS:
case RSK_SKELETON_KEY:
case RSK_BOMBCHUS_IN_LOGIC:
case RSK_TOT_ALTAR_HINT:
case RSK_GANONDORF_HINT:

View File

@ -2796,6 +2796,11 @@ u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
return Return_Item_Entry(giEntry, RG_NONE);
}
if (item == RG_SKELETON_KEY) {
Flags_SetRandomizerInf(RAND_INF_HAS_SKELETON_KEY);
return Return_Item_Entry(giEntry, RG_NONE);
}
if (item == RG_DEKU_STICK_BAG) {
Inventory_ChangeUpgrade(UPG_STICKS, 1);
INV_CONTENT(ITEM_STICK) = ITEM_STICK;
@ -5370,6 +5375,8 @@ void Interface_Draw(PlayState* play) {
}
}
//when having the skeleton key in rando, don't render the small key counter
if (!Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY)) {
switch (play->sceneNum) {
case SCENE_FOREST_TEMPLE:
case SCENE_FIRE_TEMPLE:
@ -5385,6 +5392,7 @@ void Interface_Draw(PlayState* play) {
case SCENE_GANONS_TOWER_COLLAPSE_INTERIOR:
case SCENE_INSIDE_GANONS_CASTLE_COLLAPSE:
case SCENE_TREASURE_BOX_SHOP:
if (gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] >= 0) {
s16 X_Margins_SKC;
s16 Y_Margins_SKC;
@ -5448,6 +5456,7 @@ void Interface_Draw(PlayState* play) {
default:
break;
}
}
// Rupee Counter
gDPPipeSync(OVERLAY_DISP++);

View File

@ -57,6 +57,9 @@ typedef struct EnDoor {
/* 0x01D4 */ EnDoorActionFunc actionFunc;
} EnDoor; // size = 0x01D8
#ifdef __cplusplus
extern "C"
#endif
void EnDoor_SetupType(EnDoor* enDoor, PlayState* play);
#endif