Reindeer games additions (#33)

* Remove hat from darunia

* Update some stuffs

* Remove custom collectible (in favor of CustomItem)

* Roguelike

Co-authored-by: Caladius <Caladius@users.noreply.github.com>
Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com>

* Fix a bunch of stuff

* Persist roguelike data

* Experimental anchor tweaks

* Stop freeze from scrubs & fix pablo cvar

---------

Co-authored-by: Caladius <Caladius@users.noreply.github.com>
Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com>
This commit is contained in:
Garrett Cox
2025-12-06 10:45:43 -06:00
committed by GitHub
parent e3029a834b
commit f5af415098
116 changed files with 4375 additions and 474 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+3
View File
@@ -71,6 +71,9 @@ static const ALIGN_ASSET(2) char gTitleBossRushSubtitleTex[] = dgTitleBossRushSu
#define dgTitleArchipelagoSubtilteTex "__OTR__objects/object_mag/gTitleArchipelagoSubtitleTex"
static const ALIGN_ASSET(2) char gTitleArchipelagoSubtitleTex[] = dgTitleArchipelagoSubtilteTex;
#define dgTitleRogueLikeSubtitleTex "__OTR__objects/object_mag/gTitleRogueLikeSubtitleTex"
static const ALIGN_ASSET(2) char gTitleRogueLikeSubtitleTex[] = dgTitleRogueLikeSubtitleTex;
#define dgOcarinaAButtonDL "__OTR__objects/object_ocarina_a_button/gOcarinaAButtonDL"
static const ALIGN_ASSET(2) char gOcarinaAButtonDL[] = dgOcarinaAButtonDL;
+4 -4
View File
@@ -459,10 +459,10 @@ typedef enum {
/* 0x79 */ GI_NUT_UPGRADE_30,
/* 0x7A */ GI_NUT_UPGRADE_40,
/* 0x7B */ GI_BULLET_BAG_50,
/* 0x7C */ GI_SHIP, // SOH [Enhancement] Added to enable custom item gives
/* 0x7D */ GI_ICE_TRAP, // freezes link when opened from a chest
/* 0x7E */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg)
/* 0x7F */ GI_MAX
/* 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_SHIP, // SOH [Enhancement] Added to enable custom item gives
/* 0x7E */ GI_MAX
} GetItemID;
typedef enum {
+13
View File
@@ -8,6 +8,7 @@
#include "soh/Enhancements/gameplaystats.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include "soh/Enhancements/RogueLike/Types.h"
#define FULL_HEART_HEALTH 0x10
#define STARTING_HEALTH (3 * FULL_HEART_HEALTH)
@@ -73,6 +74,7 @@ typedef enum { // Pre-existing IDs for save sections in base code
SECTION_ID_SCENES,
SECTION_ID_TRACKER_DATA,
SECTION_ID_ARCHIPELAGO,
SECTION_ID_ROGUELIKE,
SECTION_ID_MAX
} SaveFuncIDs;
@@ -167,6 +169,14 @@ typedef struct ShipRandomizerSaveContextData {
u8 triforcePiecesCollected;
} ShipRandomizerSaveContextData;
typedef struct ShipRogueLikeSaveContextData {
u32 stats[RL_MAX];
u32 xp;
u32 difficulty;
uint64_t lastActivity;
RogueLikeQuestObject quests[RL_QUEST_ID_MAX];
} ShipRogueLikeSaveContextData;
typedef struct ShipBossRushSaveContextData {
u32 isPaused;
u8 options[BR_OPTIONS_MAX];
@@ -191,6 +201,7 @@ typedef struct ShipQuestSpecificSaveContextData {
ShipRandomizerSaveContextData randomizer;
ShipBossRushSaveContextData bossRush;
ShipArchipelagoSaveContextData archipelago;
ShipRogueLikeSaveContextData rogueLike;
} ShipQuestSpecificSaveContextData;
typedef struct ShipQuestSaveContextData {
@@ -335,6 +346,7 @@ typedef enum {
/* 02 */ QUEST_RANDOMIZER,
/* 03 */ QUEST_BOSSRUSH,
/* 04 */ QUEST_ARCHIPELAGO,
/* 04 */ QUEST_ROGUELIKE,
} Quest;
#define IS_VANILLA (gSaveContext.ship.quest.id == QUEST_NORMAL)
@@ -342,6 +354,7 @@ typedef enum {
#define IS_RANDO (gSaveContext.ship.quest.id == QUEST_RANDOMIZER)
#define IS_BOSS_RUSH (gSaveContext.ship.quest.id == QUEST_BOSSRUSH)
#define IS_ARCHIPELAGO (gSaveContext.ship.quest.data.archipelago.isArchipelago == 1)
#define IS_ROGUELIKE (gSaveContext.ship.quest.id == QUEST_ROGUELIKE)
typedef enum {
/* 0x00 */ BTN_ENABLED,
+3 -5
View File
@@ -12,8 +12,7 @@ extern "C" {
extern PlayState* gPlayState;
}
#define AUTHOR "AGreenSpoon"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
void EnGs_Evil(EnGs* enGs, PlayState* play) {
Player* player = GET_PLAYER(gPlayState);
@@ -62,11 +61,10 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Evil Gossip Stone", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("EvilGossipStone"))
.Options(UIWidgets::CheckboxOptions().Tooltip("Don't you dare talk to them."));
.Options(UIWidgets::CheckboxOptions().Tooltip("Gossip stones become hostile after being spoken to."));
}
static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("EvilGossipStone") });
+2 -4
View File
@@ -15,8 +15,7 @@ extern "C" {
extern PlayState* gPlayState;
}
#define AUTHOR "Archez"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static bool sSkipNextLimb = false;
static bool sSkipNextSkeleton = false;
@@ -108,8 +107,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Visual", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Snow Golems", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("SnowGolems"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
+41 -50
View File
@@ -6,6 +6,7 @@
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/frame_interpolation.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh_assets.h"
extern "C" {
@@ -16,8 +17,7 @@ extern PlayState* gPlayState;
uint64_t GetUnixTimestamp();
}
#define AUTHOR "Caladius"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
bool isFeverDisabled = false;
bool isExchangeDisabled = false;
@@ -25,7 +25,8 @@ static float fontScale = 1.0f;
extern GetItemEntry vanillaQueuedItemEntry;
std::vector<ActorID> boulderList = { ACTOR_OBJ_BOMBIWA, ACTOR_BG_ICE_SHELTER, ACTOR_EN_ISHI, ACTOR_OBJ_HAMISHI };
std::vector<ActorID> boulderList = { ACTOR_OBJ_BOMBIWA, ACTOR_BG_ICE_SHELTER, ACTOR_EN_ISHI, ACTOR_EN_ISHI,
ACTOR_OBJ_HAMISHI };
std::string formatTimestampIceTrapFever(uint32_t value) {
uint32_t sec = value / 10;
@@ -69,17 +70,30 @@ s32 ActorSnapToFloor(Actor* refActor, PlayState* play, f32 arg2) {
void RandomizeBoulder(Actor* refActor) {
Actor* actor = (Actor*)refActor;
int16_t param = actor->params;
int16_t param = 0;
int32_t yAdj = 0;
uint32_t roll = rand() % boulderList.size();
int32_t seed = gPlayState->sceneNum + actor->id + ((int32_t)(actor->world.pos.x * 10)) +
((int32_t)(actor->world.pos.y * 10)) + ((int32_t)(actor->world.pos.z * 10)) + actor->params;
uint32_t finalSeed =
ABS(seed) + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt);
Random_Init(finalSeed);
uint32_t roll = Random(0, boulderList.size());
u32 flag = actor->id == ACTOR_EN_ISHI ? ((actor->params >> 0xA) & 0x3C) | ((actor->params >> 6) & 3)
: actor->params & 0x3F;
if (boulderList[roll] == ACTOR_EN_ISHI) {
param = 3;
param = (Random(0, 2)) | ((flag & 0x3C) << 10) | ((flag & 3) << 6);
} else {
param = flag;
}
yAdj = ActorSnapToFloor(actor, gPlayState, 0.0f);
Actor_Spawn(&gPlayState->actorCtx, gPlayState, boulderList[roll], actor->world.pos.x,
ActorSnapToFloor(actor, gPlayState, 0.0f), actor->world.pos.z, 0, 0, 0, param, false);
Actor_Kill(actor);
}
bool spawningPresents = false;
@@ -216,21 +230,20 @@ static void OnPresentChange() {
});
}
static bool isRandomizingBoulder = false;
static void OnBlitzChange() {
COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("Blitz.Enabled"), 0), []() {
if (!gPlayState) {
COND_HOOK(ShouldActorInit, CVarGetInteger(CVAR("Blitz.Enabled"), 0), [](void* actorRef, bool* should) {
if (isRandomizingBoulder)
return;
}
ActorListEntry boulders = gPlayState->actorCtx.actorLists[ACTORCAT_PROP];
Actor* currentActor = boulders.head;
if (currentActor != nullptr) {
while (currentActor != nullptr) {
for (auto& boulderActor : boulderList) {
if (currentActor->id == boulderActor) {
RandomizeBoulder(currentActor);
}
}
currentActor = currentActor->next;
Actor* actor = (Actor*)actorRef;
for (auto& boulderActor : boulderList) {
if (actor->id == boulderActor) {
isRandomizingBoulder = true;
RandomizeBoulder(actor);
isRandomizingBoulder = false;
*should = false;
return;
}
}
});
@@ -242,12 +255,6 @@ static void OnFeverConfigurationChanged() {
if (fontScale < 1.0f) {
fontScale = 1.0f;
}
if (CVarGetInteger(CVAR("ExtendTimer"), 0) < 1) {
CVarSetInteger(CVAR("ExtendTimer"), 1);
}
if (CVarGetInteger(CVAR("StartTimer"), 0) < 1) {
CVarSetInteger(CVAR("StartTimer"), 1);
}
}
void CaladiusWindow::Draw() {
@@ -271,18 +278,13 @@ void CaladiusWindow::Draw() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 };
SohGui::mSohMenu->AddWidget(path, "Holiday Fever", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Fever.Enabled"))
.Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); })
.Options(UIWidgets::CheckboxOptions().Tooltip("Can you beat your objective before the Fever sets in?\n"
"- Obtaining Ice Traps extends your timer."));
SohGui::mSohMenu->AddWidget(path, "Font: %.1fx", WIDGET_CVAR_SLIDER_FLOAT)
.CVar(CVAR("FontScale"))
.Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); })
.PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Fever.Enabled"), 0); })
.Options(UIWidgets::FloatSliderOptions().DefaultValue(1.0f).Min(1.0f).Max(5.0f));
.Options(UIWidgets::CheckboxOptions().Tooltip(
"Death will come for you when the timer runs out? Obtaining Ice Traps extends your timer. \n\nShould be "
"enabled before starting a new file, won't work well with existing files."));
SohGui::mSohMenu->AddWidget(path, "Starting Timer: %d minutes", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("StartTimer"))
.Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); })
@@ -294,24 +296,13 @@ static void RegisterMenu() {
.PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Fever.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(5).Min(1).Max(10));
SohGui::mSohMenu->AddWidget(path, "BoulderBlitzSect", WIDGET_SEPARATOR);
SohGui::mSohMenu->AddWidget(path, "Boulder Blitz", WIDGET_CVAR_CHECKBOX)
path.column = SECTION_COLUMN_1;
SohGui::mSohMenu->AddWidget(path, "Shuffle Boulders & Ice", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Blitz.Enabled"))
.Callback([](WidgetInfo& info) { OnBlitzChange(); })
.Options(UIWidgets::CheckboxOptions().Tooltip("Boulders will randomly be replaced with other boulder types."));
SohGui::mSohMenu->AddWidget(path, "OrnamentExchSect", WIDGET_SEPARATOR);
SohGui::mSohMenu->AddWidget(path, "Ornament Exchange", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("OrnExch.Enabled"))
.Callback([](WidgetInfo& info) { OnPresentChange(); })
.Options(UIWidgets::CheckboxOptions().Tooltip(
"See Malon as Young Link in Lon Lon Ranch to exchange Gifts for Ornaments!"));
SohGui::mSohMenu->AddWidget(path, "Gifts Required: %d Gifts", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("OrnExch.Amount"))
.Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); })
.PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("OrnExch.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(15).Min(5).Max(30));
"Boulders & Ice will randomly be replaced with other boulders & ice when the scene loads."));
}
static void RegisterMod() {
+7 -86
View File
@@ -7,8 +7,8 @@
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/Enhancements/randomizer/location_access.h"
#include "soh/Enhancements/randomizer/entrance.h"
#include "soh/Enhancements/custom-collectible/CustomCollectible.h"
#include "soh/Notification/Notification.h"
#include "soh/Enhancements/custom-item/CustomItem.h"
#include "soh/Enhancements/nametag.h"
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
@@ -28,8 +28,7 @@ void DoorAna_GrabPlayer(DoorAna* doorAna, PlayState* play);
}
extern GetItemEntry vanillaQueuedItemEntry;
#define AUTHOR "Fredomato"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static CollisionPoly snowballPoly;
static f32 raycastResult;
@@ -165,20 +164,6 @@ static void SpawnRandomGrotto() {
}
void SpawnStick(Vec3f pos) {
CustomCollectible::Spawn(
pos.x, pos.y + 150.0f, pos.z, 0, CustomCollectible::KILL_ON_TOUCH | CustomCollectible::TOSS_ON_SPAWN, 0,
[](Actor* actor, PlayState* play) {
FredsQuestWoodOnHand++;
Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
},
[](Actor* actor, PlayState* play) {
Matrix_Scale(40.0f, 40.0f, 40.0f, MTXMODE_APPLY);
for (int i = 4; i < 7; i++) {
Matrix_RotateZYX(800 * i, 0, 800 * i, MTXMODE_APPLY);
GetItem_Draw(play, GID_STICK);
}
});
}
Actor* specialTree = nullptr;
@@ -269,8 +254,6 @@ void DrawCrazyTaxiArrow(Actor* actor, PlayState* play) {
}
void SpawnCrazyTaxiArrow() {
EnItem00* arrow = CustomCollectible::Spawn(0, 0, 0, 0, CustomCollectible::KEEP_ON_PLAYER, 0, NULL, NULL);
arrow->actor.draw = DrawCrazyTaxiArrow;
}
void CollectionPoint_Update(Actor* actor, PlayState* play) {
@@ -339,13 +322,6 @@ void CollectionPoint_Draw(Actor* actor, PlayState* play) {
}
void SpawnCollectionPoint() {
EnItem00* collectionPoint = CustomCollectible::Spawn(859.0f, 347.0f, 5185.0f, 0xB000, 0, 0, NULL, NULL);
collectionPoint->actor.update = CollectionPoint_Update;
collectionPoint->actor.draw = CollectionPoint_Draw;
collectionPoint->actor.flags |= ACTOR_FLAG_DRAW_CULLING_DISABLED;
SkelAnime_InitFlex(gPlayState, &collectionPointSkelAnime, (FlexSkeletonHeader*)&object_toryo_Skel_007150,
(AnimationHeader*)&object_toryo_Anim_000E50, collectionPointJointTable,
collectionPointMorphTable, 17);
}
void RandomTrap_Update(Actor* actor, PlayState* play) {
@@ -398,8 +374,7 @@ void RandomTrap_Draw(Actor* actor, PlayState* play) {
void SpawnRandomTrap() {
Vec3f pos = FindValidPos(2000.0f);
EnItem00* randomTrap =
CustomCollectible::Spawn(pos.x, pos.y, pos.z, 0, CustomCollectible::TOSS_ON_SPAWN, 0, NULL, NULL);
EnItem00* randomTrap = CustomItem::Spawn(pos.x, pos.y, pos.z, 0, CustomItem::TOSS_ON_SPAWN, 0, NULL, NULL);
SoundSource_PlaySfxAtFixedWorldPos(gPlayState, &randomTrap->actor.world.pos, 20, NA_SE_EV_LIGHTNING);
randomTrap->actor.update = RandomTrap_Update;
randomTrap->actor.draw = RandomTrap_Draw;
@@ -448,67 +423,13 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 };
// UIWidgets::EnhancementSliderFloat("Xfloat", "Xfloat", CVAR("tmpxf"), 0.0f, 10.0f, "%.2f", 1.0f, false);
// UIWidgets::EnhancementSliderFloat("Yfloat", "Yfloat", CVAR("tmpyf"), 0.0f, 10.0f, "%.2f", 1.0f, false);
// UIWidgets::EnhancementSliderFloat("Zfloat", "Zfloat", CVAR("tmpzf"), 0.0f, 10.0f, "%.2f", 1.0f, false);
// UIWidgets::EnhancementSliderInt("Xs", "Xs", CVAR("tmpxs"), 0, UINT16_MAX, "%d", 1, false);
// UIWidgets::EnhancementSliderInt("Ys", "Ys", CVAR("tmpys"), 0, UINT16_MAX, "%d", 1, false);
// UIWidgets::EnhancementSliderInt("Zs", "Zs", CVAR("tmpzs"), 0, UINT16_MAX, "%d", 1, false);
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
SohGui::mSohMenu->AddWidget(path, "Fred's Quest", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("FredsQuest.Enabled"))
.Options(UIWidgets::CheckboxOptions().Tooltip(
"Collect wood and bring it to the collection point in Hyrule Field for a small reward."));
SohGui::mSohMenu->AddWidget(path, "Crazy Taxi Arrow", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("FredsQuest.CrazyTaxiArrow"))
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::CheckboxOptions().Tooltip(
"Collect wood and bring it to the collection point in Hyrule Field for a small reward."));
SohGui::mSohMenu->AddWidget(path, "Wood Needed", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("FredsQuest.WoodNeeded"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(300).Min(0).Max(1000));
SohGui::mSohMenu->AddWidget(path, "Tree Bonk Drop Rate", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("FredsQuest.TreeBonkDropRate"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(1).Min(0).Max(10));
SohGui::mSohMenu->AddWidget(path, "Tree Break Drop Rate", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("FredsQuest.TreeBreakDropRate"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(3).Min(0).Max(50));
SohGui::mSohMenu->AddWidget(path, "Special Break Drop Rate", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("FredsQuest.SpecialBreakDropRate"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(10).Min(0).Max(50));
SohGui::mSohMenu->AddWidget(path, "Encumbered Threshold", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("FredsQuest.EncumberedThreshold"))
.Callback([](WidgetInfo& info) { OnConfigurationChanged(); })
.PreFunc(
[](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("FredsQuest.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(60).Min(0).Max(200).Tooltip(
"If you have more than this many sticks, you will be encumbered and run slower. 0 for disabled"));
SohGui::mSohMenu->AddWidget(path, "Random Traps", WIDGET_CVAR_CHECKBOX)
SohGui::mSohMenu->AddWidget(path, "Chasing Knockback Spikes", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("RandomTraps.Enabled"))
.Options(UIWidgets::CheckboxOptions().Tooltip(
"Random traps will spawn around you at a configurable rate. (Currently only knockback)"));
"Random spikes will spawn around you at a configurable rate, chasing you for a short time before "
"disappearing. If they touch you, you get knocked back."));
SohGui::mSohMenu->AddWidget(path, "Trap Lifetime (Seconds)", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("RandomTraps.Lifetime"))
+26 -46
View File
@@ -15,10 +15,10 @@ void func_80ABBBA8(EnNutsball* nut, PlayState* play);
void EnNutsball_Draw(Actor* nut, PlayState* play);
}
#define AUTHOR "Grimey"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static bool spawningPenguins = false;
static u32 hailstormActiveTimer = 0;
typedef enum {
PENGUIN_STATE_IDLE,
@@ -103,46 +103,31 @@ void Penguin_Destroy(Actor* actor, PlayState* play) {
static void OnConfigurationChanged() {
COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("Hailstorm"), 0), []() {
// Every frame has a 1/500 chance of spawning close hail
if (rand() % 500 == 0) {
int spawned = 0;
while (spawned < 1) {
Vec3f pos = GET_PLAYER(gPlayState)->actor.world.pos;
pos.x += (float)Random(0, 50) - 25.0f;
pos.z += (float)Random(0, 50) - 25.0f;
pos.y += 200.0f;
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_NUTSBALL, pos.x, pos.y, pos.z, 0,
0, 0, 0, false);
EnNutsball* nut = (EnNutsball*)actor;
nut->actor.draw = EnNutsball_Draw;
nut->actor.shape.rot.y = 0;
nut->timer = 0;
nut->actionFunc = func_80ABBBA8;
nut->actor.speedXZ = 0.0f;
nut->actor.gravity = -2.0f;
spawned++;
}
// Every frame has a 1/1000 chance to start a hailstorm if there isn't one already
if (hailstormActiveTimer == 0 && rand() % 1000 == 0) {
hailstormActiveTimer = 20 * 10; // Lasts for 20 seconds
}
// Every frame has a 1/50 chance of spawning far hail
if (rand() % 50 == 0) {
int spawned = 0;
while (spawned < 1) {
Vec3f pos = GET_PLAYER(gPlayState)->actor.world.pos;
pos.x += (float)Random(0, 500) - 250.0f;
pos.z += (float)Random(0, 500) - 250.0f;
pos.y += 200.0f;
if (hailstormActiveTimer > 0) {
hailstormActiveTimer--;
if (rand() % 2 == 0) {
int spawned = 0;
while (spawned < 1) {
Vec3f pos = GET_PLAYER(gPlayState)->actor.world.pos;
pos.x += (float)Random(0, 500) - 250.0f;
pos.z += (float)Random(0, 500) - 250.0f;
pos.y += 500.0f;
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_NUTSBALL, pos.x, pos.y, pos.z, 0,
0, 0, 0, false);
EnNutsball* nut = (EnNutsball*)actor;
nut->actor.draw = EnNutsball_Draw;
nut->actor.shape.rot.y = 0;
nut->timer = 0;
nut->actionFunc = func_80ABBBA8;
nut->actor.speedXZ = 0.0f;
nut->actor.gravity = -2.0f;
spawned++;
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_NUTSBALL, pos.x, pos.y,
pos.z, 0, 0, 0, 0, false);
EnNutsball* nut = (EnNutsball*)actor;
nut->actor.draw = EnNutsball_Draw;
nut->actor.shape.rot.y = 0;
nut->timer = 0;
nut->actionFunc = func_80ABBBA8;
nut->actor.speedXZ = 0.0f;
nut->actor.gravity = -2.0f;
spawned++;
}
}
}
});
@@ -209,12 +194,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
SohGui::mSohMenu->AddWidget(path, "Penguins", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Penguins"))
.Options(UIWidgets::CheckboxOptions().Tooltip("Penguins will spawn in huddles throughout hyrule"));
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Hailstorm", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Hailstorm"))
+2 -5
View File
@@ -10,9 +10,7 @@ extern PlayState* gPlayState;
// TODO: Include anything you need here from C land
}
// TODO: Change this to YourName
#define AUTHOR "LL"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static ImVec4 customColorZero = RAINBOW_PRESETS[0][0];
static ImVec4 customColorOne = RAINBOW_PRESETS[0][1];
@@ -54,8 +52,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Custom Rainbows", WIDGET_CVAR_CHECKBOX).CVar(CVAR("EnableCustomRainbows"));
+913 -24
View File
@@ -12,8 +12,7 @@ extern "C" {
extern PlayState* gPlayState;
}
#define AUTHOR "NotProxySaw"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
int dialogIndex = 0;
int affection = 0;
@@ -35,40 +34,838 @@ const std::vector<Dialog> dialogs = { { // 0
{ "I'm here to end this... peacefully.", 1, 2 },
{ "I respect a man with ambition.", -1, 3 },
} },
{ // 1
{ // 1 - Loneliness path
"Lonely? Power's my only ally.",
{
{ "There's more to you than that.", 3, 4 },
{ "I get it more than you think.", 5, 4 },
{ "Why not let someone in?", 2, 4 },
{ "I get it more than you think.", 5, 6 },
{ "Why not let someone in?", 2, 5 },
} },
{ // 2
{ // 2 - Peace path
"Peace? It's meaningless here.",
{
{ "Maybe you've forgotten peace.", 2, 4 },
{ "Power isn't everything.", 4, 4 },
{ "Ally, not enemy.. that's my goal.", 1, 4 },
{ "Maybe you've forgotten peace.", 2, 7 },
{ "Power isn't everything.", 4, 8 },
{ "Ally, not enemy.. that's my goal.", 1, 9 },
} },
{ // 3
{ // 3 - Respect path
"Respect? From you?",
{
{ "We're not so different.", 3, 4 },
{ "Maybe I admire your strength.", 5, 4 },
{ "True power must be earned.", -1, 4 },
{ "We're not so different.", 3, 10 },
{ "Maybe I admire your strength.", 5, 11 },
{ "True power must be earned.", -1, 12 },
} },
{ // 4
{ // 4 - Early convergence
"You're... different than I thought.",
{
{ "Together, we'd be unstoppable.", 5, 5 },
{ "Power won't bring fulfillment.", 3, 5 },
{ "Let's change the world.", 4, 5 },
{ "Together, we'd be unstoppable.", 4, 13 },
{ "Power won't bring fulfillment.", 3, 14 },
{ "Let's change the world.", 5, 15 },
} },
{ // 5
"What if I trusted you with power?",
{ // 5 - Vulnerability path
"No one has ever... understood before.",
{
{ "I'll protect it. And you.", 10, -1 },
{ "Together, we're unstoppable.", 5, -1 },
{ "Our way. Our history.", -3, -1 },
{ "I want to understand you.", 5, 16 },
{ "We all need someone.", 4, 17 },
{ "Your guard can come down with me.", 3, 18 },
} },
{ // 6 - Shared experience path
"You think you know my struggle?",
{
{ "I've faced darkness too.", 4, 19 },
{ "We're both outcasts.", 5, 20 },
{ "Tell me your story.", 3, 16 },
} },
{ // 7 - Memory path
"Peace... I barely remember it.",
{
{ "Let me remind you.", 5, 21 },
{ "We can create new memories.", 4, 22 },
{ "The desert remembers peace.", 2, 23 },
} },
{ // 8 - Philosophy path
"Then what IS everything?",
{
{ "Connection. Trust. Love.", 5, 24 },
{ "Finding your true purpose.", 3, 25 },
{ "Balance between power and heart.", 4, 26 },
} },
{ // 9 - Alliance path
"An ally? What could you offer me?",
{
{ "Companionship you've never known.", 5, 27 },
{ "A kingdom rebuilt together.", 3, 28 },
{ "Freedom from this endless cycle.", 4, 29 },
} },
{ // 10 - Similarity path
"Similar? Explain yourself.",
{
{ "We both fight for what we believe.", 3, 30 },
{ "We've both been betrayed.", 5, 31 },
{ "We're both stronger than we show.", 4, 32 },
} },
{ // 11 - Admiration path
"Admire? This isn't a trick?",
{
{ "Your determination is inspiring.", 5, 33 },
{ "You've overcome so much.", 4, 34 },
{ "I see the man behind the power.", 6, 35 },
} },
{ // 12 - Challenge path (lower affection)
"Then prove you've earned it!",
{
{ "I've earned the right to stand here.", 2, 36 },
{ "I'm not here to prove anything to you.", -2, 37 },
{ "My actions speak louder than words.", 3, 38 },
} },
{ // 13 - Partnership power path
"Unstoppable... together?",
{
{ "Your strength and my courage.", 5, 39 },
{ "We'd reshape Hyrule as equals.", 4, 40 },
{ "Imagine what we could achieve.", 3, 41 },
} },
{ // 14 - Fulfillment path
"What could fulfill me then?",
{
{ "Someone who sees you, truly.", 6, 42 },
{ "A purpose beyond conquest.", 3, 43 },
{ "The warmth of genuine connection.", 5, 44 },
} },
{ // 15 - Revolution path
"Change the world... how?",
{
{ "End this cycle of hatred.", 4, 45 },
{ "Build something beautiful together.", 5, 46 },
{ "Rewrite our destinies.", 6, 47 },
} },
{ // 16 - Understanding convergence
"No one has ever tried to understand...",
{
{ "I'm not no one. I'm here.", 6, 48 },
{ "Your past shaped you, not defined you.", 4, 49 },
{ "Let me be the first.", 5, 50 },
} },
{ // 17 - Need path
"Need? I've survived alone this long.",
{
{ "Surviving isn't living.", 5, 51 },
{ "You don't have to anymore.", 6, 52 },
{ "Even the strongest need support.", 4, 53 },
} },
{ // 18 - Guard down path
"Why would I lower my guard?",
{
{ "Because I'm lowering mine first.", 6, 54 },
{ "Trust has to start somewhere.", 4, 55 },
{ "You're safe with me.", 5, 56 },
} },
{ // 19 - Darkness shared path
"Your darkness... tell me.",
{
{ "I've fought my own demons.", 5, 57 },
{ "We all have shadows within us.", 4, 58 },
{ "But I chose light. You can too.", 3, 59 },
} },
{ // 20 - Outcast bond path
"Outcasts... yes. We are.",
{
{ "Then let's find belonging in each other.", 7, 60 },
{ "We don't have to be alone.", 5, 61 },
{ "Our shared pain connects us.", 4, 62 },
} },
{ // 21 - Reminder path
"How could you remind me?",
{
{ "Close your eyes. Remember the desert wind.", 5, 63 },
{ "Through moments like this one.", 6, 64 },
{ "Let me show you, not tell you.", 4, 65 },
} },
{ // 22 - New memories path
"New memories... with you?",
{
{ "Every ending is a new beginning.", 6, 66 },
{ "Yes. Better ones than you've known.", 5, 67 },
{ "We write our own story from here.", 4, 68 },
} },
{ // 23 - Desert memories path
"The desert... my home once.",
{
{ "It can be again, differently.", 4, 69 },
{ "Home is where the heart is.", 5, 70 },
{ "I'd like to see it with you.", 6, 71 },
} },
{ // 24 - Love path (high affection)
"Love? You speak of... love?",
{
{ "Is that so impossible?", 6, 72 },
{ "I think I'm falling for you.", 8, 73 },
{ "Love is the strongest power.", 5, 74 },
} },
{ // 25 - Purpose path
"My purpose was always power.",
{
{ "You're meant for more.", 5, 75 },
{ "Purpose can evolve.", 4, 76 },
{ "Find purpose in protecting, not destroying.", 3, 77 },
} },
{ // 26 - Balance path
"Balance... I've never had that.",
{
{ "I'll help you find it.", 6, 78 },
{ "We can balance each other.", 7, 79 },
{ "It's not too late to learn.", 4, 80 },
} },
{ // 27 - Companionship offer
"Companionship? What does that mean?",
{
{ "It means you're not alone anymore.", 7, 81 },
{ "Someone to share victories and defeats.", 5, 82 },
{ "A partner in every sense.", 6, 83 },
} },
{ // 28 - Kingdom rebuild path
"Rebuild? After all I've destroyed?",
{
{ "Redemption is always possible.", 5, 84 },
{ "We build on lessons learned.", 4, 85 },
{ "Your past doesn't define your future.", 6, 86 },
} },
{ // 29 - Freedom path
"Freedom from the cycle...",
{
{ "Break free with me.", 7, 87 },
{ "We choose our own fate.", 6, 88 },
{ "This doesn't have to be our story.", 5, 89 },
} },
{ // 30 - Belief path
"You fight for Hyrule. I fight for...",
{
{ "For recognition. I see you.", 6, 90 },
{ "We can fight for each other now.", 7, 91 },
{ "What we fight for can change.", 4, 92 },
} },
{ // 31 - Betrayal bond path
"Betrayed... yes. You know that pain?",
{
{ "More than you realize.", 6, 93 },
{ "But I won't betray you.", 7, 94 },
{ "We can heal together.", 5, 95 },
} },
{ // 32 - Hidden strength path
"Stronger than I show?",
{
{ "Your vulnerability is strength.", 6, 96 },
{ "True strength is opening your heart.", 7, 97 },
{ "I see both your power and pain.", 5, 98 },
} },
{ // 33 - Inspiration path
"You're... inspired by me?",
{
{ "Your resolve never wavered.", 6, 99 },
{ "Despite everything, you stood tall.", 7, 100 },
{ "That kind of strength is rare.", 5, 101 },
} },
{ // 34 - Overcome path
"I've overcome... so much pain.",
{
{ "And you don't have to face more alone.", 7, 102 },
{ "Let me help carry that burden.", 6, 103 },
{ "Your journey shaped a remarkable person.", 5, 104 },
} },
{ // 35 - True sight path (very high affection)
"You... you see me? The real me?",
{
{ "Every part of you.", 8, 105 },
{ "Behind the armor and anger, yes.", 7, 106 },
{ "And I want to know you more.", 6, 107 },
} },
{ // 36 - Earned respect path
"Perhaps you have earned something...",
{
{ "Your respect means everything.", 5, 108 },
{ "I've earned a chance.", 4, 109 },
{ "Maybe even your trust?", 6, 110 },
} },
{ // 37 - Confrontation path (recovery possible)
"Arrogant! Just like I thought!",
{
{ "Wait, I didn't mean it like that.", 3, 111 },
{ "You're right, I'm sorry.", 4, 112 },
{ "Let me start over.", 2, 113 },
} },
{ // 38 - Action path
"Actions... like coming here unarmed?",
{
{ "I'm armed with only honesty.", 6, 114 },
{ "My sword isn't drawn.", 5, 115 },
{ "I chose words over weapons.", 7, 116 },
} },
{ // 39 - Combined strength
"My strength and your courage...",
{
{ "We'd be legendary.", 6, 117 },
{ "Nothing could stand in our way.", 5, 118 },
{ "But more than that, we'd have each other.", 8, 119 },
} },
{ // 40 - Equals path
"Equals? You'd see me as equal?",
{
{ "Never as less than.", 7, 81 },
{ "Partners in every way.", 8, 83 },
{ "Two halves of a greater whole.", 6, 79 },
} },
{ // 41 - Achievement dreams
"I've imagined... but never with another.",
{
{ "Dream with me now.", 7, 66 },
{ "Our achievements, together.", 6, 82 },
{ "Let's make it real.", 8, 117 },
} },
{ // 42 - True sight deep
"Someone who sees me truly...",
{
{ "I see your scars and your dreams.", 8, 105 },
{ "Every layer, every truth.", 7, 106 },
{ "And I'm not afraid.", 9, 54 },
} },
{ // 43 - New purpose
"A purpose beyond conquest...",
{
{ "Building instead of destroying.", 6, 84 },
{ "Creating a legacy of hope.", 7, 86 },
{ "Finding joy in creation.", 5, 76 },
} },
{ // 44 - Connection warmth
"Warmth... I've been cold so long.",
{
{ "Let me warm your heart.", 9, 65 },
{ "You don't have to be cold anymore.", 8, 52 },
{ "I'll be your warmth.", 10, 56 },
} },
{ // 45 - End hatred
"End the hatred... can it be done?",
{
{ "With you by my side, yes.", 8, 88 },
{ "We start by choosing love.", 9, 74 },
{ "Together, we break the cycle.", 7, 87 },
} },
{ // 46 - Build beauty
"Something beautiful... with you?",
{
{ "The most beautiful thing.", 9, 73 },
{ "A future worth living for.", 8, 86 },
{ "Our love could heal Hyrule.", 10, 95 },
} },
{ // 47 - Rewrite destiny
"Rewrite our destinies... together...",
{
{ "Our story, our way.", 9, 68 },
{ "No prophecy can stop us.", 8, 88 },
{ "Destiny is what we make it.", 10, 119 },
} },
{ // 48 - Here for you
"You're here... for me?",
{
{ "Only for you.", 9, 73 },
{ "I chose you, Ganondorf.", 10, 94 },
{ "And I'm not leaving.", 8, 52 },
} },
{ // 49 - Past vs present
"Shaped but not defined...",
{
{ "You define yourself now.", 8, 76 },
{ "Your future is unwritten.", 7, 86 },
{ "Define yourself through love.", 9, 74 },
} },
{ // 50 - First to understand
"The first to try...",
{
{ "And I'll never stop trying.", 10, 103 },
{ "You deserve understanding.", 8, 95 },
{ "Let me be your first and last.", 9, 61 },
} },
{ // 51 - Living vs surviving
"Living... what is that like?",
{
{ "Let me show you.", 9, 65 },
{ "It's everything you've missed.", 8, 64 },
{ "We'll discover it together.", 10, 66 },
} },
{ // 52 - No more alone
"I don't... have to be alone?",
{
{ "Never again.", 10, 81 },
{ "I'm here, always.", 9, 102 },
{ "We'll face everything together.", 8, 53 },
} },
{ // 53 - Strongest need support
"Even the strongest...",
{
{ "Especially the strongest.", 8, 103 },
{ "Let me be your strength.", 9, 97 },
{ "We're stronger together.", 10, 119 },
} },
{ // 54 - Mutual vulnerability
"You'd... lower yours first?",
{
{ "I already have.", 10, 94 },
{ "My heart is open to you.", 9, 97 },
{ "I trust you completely.", 11, 110 },
} },
{ // 55 - Trust begins
"Trust... starting here?",
{
{ "Right here, right now.", 9, 64 },
{ "With us, with this moment.", 10, 94 },
{ "I trust you with everything.", 8, 110 },
} },
{ // 56 - Safe haven
"Safe... with you?",
{
{ "Always. I promise.", 11, 94 },
{ "I'll protect your heart.", 10, 103 },
{ "You're safe in my arms.", 12, 60 },
} },
{ // 57 - Shared demons
"Your demons... like mine?",
{
{ "We can fight them together.", 9, 53 },
{ "They don't control us anymore.", 8, 59 },
{ "Love conquers all darkness.", 10, 74 },
} },
{ // 58 - Universal shadows
"Shadows within us all...",
{
{ "But you bring light to mine.", 10, 63 },
{ "Let's be each other's light.", 11, 79 },
{ "Together we shine brighter.", 9, 118 },
} },
{ // 59 - Choose light
"I could... choose light?",
{
{ "You already are, by listening.", 9, 48 },
{ "Choose me, choose light.", 10, 88 },
{ "It's never too late.", 8, 80 },
} },
{ // 60 - Belonging together
"Belonging... in each other...",
{
{ "You belong with me.", 12, 83 },
{ "We're home in each other.", 11, 119 },
{ "My heart is your home.", 13, 119 },
} },
{ // 61 - Together forever
"Not alone... anymore...",
{
{ "Never, ever alone.", 11, 102 },
{ "I'll always be here.", 10, 119 },
{ "Forever together.", 12, 119 },
} },
{ // 62 - Connected pain
"Our shared pain connects us...",
{
{ "Now let joy connect us.", 10, 117 },
{ "Pain brought us here, love keeps us.", 11, 95 },
{ "From pain to paradise.", 9, 51 },
} },
{ // 63 - Desert meditation
"The desert wind... I remember...",
{
{ "Hold onto that feeling.", 9, 64 },
{ "Peace can return.", 10, 69 },
{ "I'll be your peace.", 11, 56 },
} },
{ // 64 - This moment
"This moment... it feels...",
{
{ "Like coming home?", 10, 79 },
{ "Like the start of something beautiful?", 11, 66 },
{ "Right. It feels right.", 12, 79 },
} },
{ // 65 - Show not tell
"Show me... how?",
{
{ "Through actions, through devotion.", 10, 114 },
{ "Every day, every moment.", 11, 102 },
{ "Let me love you.", 12, 73 },
} },
{ // 66 - New beginning
"A new beginning... with you...",
{
{ "Our greatest adventure.", 11, 117 },
{ "Better than any ending.", 10, 117 },
{ "The best is yet to come.", 12, 89 },
} },
{ // 67 - Better memories
"Better memories... I want that.",
{
{ "Then take my hand.", 12, 119 },
{ "We'll make them together.", 11, 68 },
{ "Starting right now.", 10, 64 },
} },
{ // 68 - Write our story
"Our story... from here...",
{
{ "The greatest story ever told.", 11, 117 },
{ "Written in love, not war.", 12, 116 },
{ "A story for the ages.", 10, 89 },
} },
{ // 69 - Home differently
"Home, differently... explain.",
{
{ "With love, not loneliness.", 10, 119 },
{ "Together, not alone.", 11, 119 },
{ "Our home, our rules.", 9, 71 },
} },
{ // 70 - Heart is home
"Where the heart is...",
{
{ "My heart is with you.", 12, 83 },
{ "You are my home.", 13, 119 },
{ "Home is in your eyes.", 11, 105 },
} },
{ // 71 - See together
"You'd... visit my homeland with me?",
{
{ "Anywhere you go, I go.", 12, 61 },
{ "I want to see your world.", 11, 107 },
{ "Your past is part of our future.", 10, 86 },
} },
{ // 72 - Love not impossible
"Not impossible... but unexpected.",
{
{ "The best things are unexpected.", 11, 117 },
{ "Sometimes fate surprises us.", 10, 88 },
{ "I love you, Ganondorf.", 13, 73 },
} },
{ // 73 - Falling confession
"You're... falling for me?",
{
{ "I've already fallen.", 14, -1 },
{ "Completely, utterly, truly.", 13, -1 },
{ "Catch me?", 12, -1 },
} },
{ // 74 - Love strongest power
"Love... the strongest power...",
{
{ "And it's ours to share.", 12, 119 },
{ "More powerful than the Triforce.", 13, -1 },
{ "Let love be our strength.", 11, 97 },
} },
{ // 75 - Meant for more
"Meant for more... perhaps...",
{
{ "I know you are.", 10, 76 },
{ "You're meant for greatness and love.", 11, 101 },
{ "Let me prove it to you.", 9, 65 },
} },
{ // 76 - Purpose evolves
"Purpose can evolve...",
{
{ "Like we evolve together.", 11, 78 },
{ "Grow with me.", 10, 117 },
{ "Our purpose is each other.", 12, 83 },
} },
{ // 77 - Protect not destroy
"Protect... instead of destroy...",
{
{ "Protect what we build together.", 10, 84 },
{ "Protect each other's hearts.", 11, 103 },
{ "I'll teach you, if you teach me.", 9, 78 },
} },
{ // 78 - Help find balance
"You'll help me find it?",
{
{ "Every step of the way.", 11, 102 },
{ "We'll find it together.", 10, 79 },
{ "I'm already helping, see?", 12, 64 },
} },
{ // 79 - Balance each other
"Balance each other...",
{
{ "Perfect harmony.", 12, 117 },
{ "Yin and yang.", 11, 119 },
{ "Two souls, one heart.", 13, 119 },
} },
{ // 80 - Never too late
"Never too late... truly?",
{
{ "For love? Never.", 12, 119 },
{ "You're here now, that's what matters.", 11, 64 },
{ "This is your moment.", 10, 88 },
} },
{ // 81 - Not alone anymore
"Not alone... anymore...",
{
{ "You have me, always.", 12, 102 },
{ "We have each other.", 13, 119 },
{ "Forever and always.", 11, 119 },
} },
{ // 82 - Share everything
"Share victories and defeats...",
{
{ "Everything, together.", 11, 119 },
{ "In good times and bad.", 12, 83 },
{ "Till the end of time.", 10, 119 },
} },
{ // 83 - Partner every sense
"Partner in every sense...",
{
{ "In battle and in love.", 12, 91 },
{ "Soul mates.", 13, 119 },
{ "My other half.", 14, -1 },
} },
{ // 84 - Redemption possible
"Redemption... for me?",
{
{ "For everyone. Especially you.", 11, 95 },
{ "I believe in you.", 12, 109 },
{ "Love redeems all.", 10, 74 },
} },
{ // 85 - Build on lessons
"Lessons learned... yes...",
{
{ "Wisdom through experience.", 10, 76 },
{ "Our past guides our future.", 11, 86 },
{ "Together we're wiser.", 9, 78 },
} },
{ // 86 - Future undefined
"My future... undefined...",
{
{ "Let's define it together.", 12, 89 },
{ "A blank canvas for us to paint.", 11, 68 },
{ "Our future is love.", 13, 119 },
} },
{ // 87 - Break free together
"Break free... with you...",
{
{ "Hand in hand.", 13, 119 },
{ "Into a new dawn.", 12, 117 },
{ "Freedom through love.", 14, -1 },
} },
{ // 88 - Choose fate
"We choose... our own fate...",
{
{ "And I choose you.", 14, -1 },
{ "Our fate is our love.", 13, 119 },
{ "Destiny be damned.", 12, 89 },
} },
{ // 89 - Not our story
"This doesn't have to be our story...",
{
{ "We write a better one.", 13, 117 },
{ "Our love story.", 14, -1 },
{ "A story of hope.", 12, 117 },
} },
{ // 90 - Recognition found
"You... see me...",
{
{ "I see you, I love you.", 13, 105 },
{ "All of you.", 12, 98 },
{ "And I always will.", 14, -1 },
} },
{ // 91 - Fight for each other
"Fight for each other...",
{
{ "With each other.", 13, 119 },
{ "Side by side.", 12, 117 },
{ "Because of each other.", 14, -1 },
} },
{ // 92 - What we fight changes
"What we fight for... changes...",
{
{ "Love changes everything.", 13, 95 },
{ "You've changed me.", 12, 104 },
{ "We've changed each other.", 14, -1 },
} },
{ // 93 - Know betrayal pain
"You know that pain...",
{
{ "But we heal together.", 12, 95 },
{ "No more betrayal, only trust.", 13, 110 },
{ "Our bond is unbreakable.", 11, 94 },
} },
{ // 94 - Won't betray promise
"You won't... betray me?",
{
{ "Never. I swear it.", 14, -1 },
{ "You have my word, my heart.", 13, 119 },
{ "I'd sooner die.", 12, 102 },
} },
{ // 95 - Heal together
"Heal... together...",
{
{ "Our love is the remedy.", 13, 119 },
{ "Time and trust heal all.", 12, 110 },
{ "I'll help you heal.", 14, -1 },
} },
{ // 96 - Vulnerability strength
"Vulnerability... is strength?",
{
{ "The greatest strength.", 12, 101 },
{ "It takes courage to be vulnerable.", 13, 99 },
{ "With me, you can be vulnerable.", 14, -1 },
} },
{ // 97 - Open heart strength
"Opening my heart...",
{
{ "Is the bravest thing you'll do.", 13, 99 },
{ "I'll cherish it always.", 14, -1 },
{ "Your heart is safe with me.", 15, -1 },
} },
{ // 98 - Power and pain
"My power and pain...",
{
{ "Both make you who you are.", 12, 104 },
{ "I love all of you.", 14, -1 },
{ "Let me ease your pain.", 13, 95 },
} },
{ // 99 - Unwavering resolve
"My resolve... you noticed?",
{
{ "How could I not?", 12, 100 },
{ "It's magnificent.", 13, 101 },
{ "It's one of many things I love.", 14, -1 },
} },
{ // 100 - Stood tall
"I stood tall... yes...",
{
{ "And you still stand tall.", 13, 101 },
{ "Now stand with me.", 14, -1 },
{ "Together we stand taller.", 12, 118 },
} },
{ // 101 - Rare strength
"Rare strength...",
{
{ "Matched only by your capacity to love.", 13, 104 },
{ "Strength I admire and love.", 14, -1 },
{ "Let me be worthy of it.", 12, 109 },
} },
{ // 102 - No more alone
"No more... alone...",
{
{ "Not while I breathe.", 14, -1 },
{ "I'm here, forever.", 15, -1 },
{ "Always together.", 13, 119 },
} },
{ // 103 - Carry burden
"Help carry... my burden?",
{
{ "I'd carry it all if I could.", 14, -1 },
{ "Your burden is my burden.", 13, 119 },
{ "Share it with me.", 15, -1 },
} },
{ // 104 - Remarkable person
"Remarkable... person?",
{
{ "The most remarkable I've known.", 14, -1 },
{ "You're extraordinary.", 15, -1 },
{ "And you're mine.", 13, 119 },
} },
{ // 105 - Every part
"Every part... of me?",
{
{ "Every. Single. Part.", 16, -1 },
{ "The good, the bad, all of it.", 15, -1 },
{ "I love all of you.", 17, -1 },
} },
{ // 106 - Behind armor
"Behind the armor... you see...",
{
{ "The man I love.", 16, -1 },
{ "Your true self.", 15, -1 },
{ "The one meant for me.", 17, -1 },
} },
{ // 107 - Know more
"Know me more...",
{
{ "Spend eternity learning you.", 16, -1 },
{ "Every day, something new.", 15, -1 },
{ "I'll never stop discovering you.", 17, -1 },
} },
{ // 108 - Respect means everything
"Means everything...",
{
{ "Then have all of it.", 13, 90 },
{ "You have my respect and love.", 14, -1 },
{ "You've earned both.", 12, 109 },
} },
{ // 109 - Earned chance
"A chance... yes...",
{
{ "A chance at love.", 13, 72 },
{ "A chance at happiness.", 14, -1 },
{ "Take it. Take me.", 15, -1 },
} },
{ // 110 - Maybe trust
"Maybe... even trust...",
{
{ "Especially trust.", 14, -1 },
{ "Trust me with your heart.", 15, -1 },
{ "I trust you with mine.", 13, 119 },
} },
{ // 111 - Clarification
"You didn't mean...",
{
{ "I meant I admire you.", 11, 33 },
{ "Let me explain better.", 10, 48 },
{ "I'm nervous around you.", 12, 72 },
} },
{ // 112 - Apology path
"You're... sorry?",
{
{ "Deeply. I care about you.", 12, 48 },
{ "I don't want to fight.", 11, 45 },
{ "I want to love you.", 13, 72 },
} },
{ // 113 - Start over
"Start over...",
{
{ "Hi. I'm Link. And I love you.", 13, 73 },
{ "Let me show you my heart.", 12, 97 },
{ "Give me another chance?", 11, 109 },
} },
{ // 114 - Armed with honesty
"Honesty... that's rare.",
{
{ "I'll always be honest with you.", 13, 94 },
{ "Honesty and love.", 14, -1 },
{ "The truth is I love you.", 12, 73 },
} },
{ // 115 - Sword not drawn
"Your sword... sheathed...",
{
{ "I don't need it with you.", 13, 54 },
{ "My heart is my weapon now.", 14, -1 },
{ "I fight for you, not against you.", 12, 91 },
} },
{ // 116 - Words over weapons
"Words over weapons...",
{
{ "Words of love.", 14, -1 },
{ "The pen is mightier, they say.", 12, 114 },
{ "Let love be our language.", 15, -1 },
} },
{ // 117 - Legendary
"Legendary... yes...",
{
{ "A legend of love.", 14, -1 },
{ "Our legend.", 15, -1 },
{ "Written in the stars.", 13, 119 },
} },
{ // 118 - Nothing in way
"Nothing could stand...",
{
{ "Against our love.", 14, -1 },
{ "We're unstoppable together.", 13, 119 },
{ "Invincible.", 15, -1 },
} },
{ // 119 - Have each other (high affection)
"We'd have each other...",
{
{ "That's all that matters.", 17, -1 },
{ "The greatest treasure.", 16, -1 },
{ "My heart is yours.", 18, -1 },
} } };
static void OnConfigurationChanged() {
@@ -122,8 +919,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Ganon Dating Sim", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("GanonDatingSim"))
@@ -132,4 +928,97 @@ static void RegisterMenu() {
}
static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("GanonDatingSim") });
// static RegisterShipInitFunc initFunc2([]() {
// // Validate dialog tree doesn't have any cycles, invalid indices, unreachable nodes, or dead ends.
// std::set<int> reachableNodes;
// std::set<int> visitedInPath;
// // Check for invalid indices and cycles using DFS
// std::function<bool(int, std::set<int>&)> validateNode = [&](int nodeIndex, std::set<int>& currentPath) -> bool {
// if (nodeIndex == -1) return true; // Valid end
// if (nodeIndex < 0 || nodeIndex >= (int)dialogs.size()) {
// SPDLOG_ERROR("[Ganon Dating Sim] Invalid dialog index: {}", nodeIndex);
// return false;
// }
// if (currentPath.count(nodeIndex)) {
// SPDLOG_ERROR("[Ganon Dating Sim] Cycle detected at node {}", nodeIndex);
// return false;
// }
// reachableNodes.insert(nodeIndex);
// currentPath.insert(nodeIndex);
// const auto& dialog = dialogs[nodeIndex];
// if (dialog.options.size() != 3) {
// SPDLOG_ERROR("[Ganon Dating Sim] Node {} doesn't have exactly 3 options", nodeIndex);
// return false;
// }
// for (const auto& option : dialog.options) {
// if (!validateNode(option.nextDialogIndex, currentPath)) {
// return false;
// }
// }
// currentPath.erase(nodeIndex);
// return true;
// };
// // Start validation from root node (0)
// std::set<int> path;
// if (!validateNode(0, path)) {
// SPDLOG_ERROR("[Ganon Dating Sim] Dialog tree validation failed!");
// }
// // Check for unreachable nodes
// for (size_t i = 0; i < dialogs.size(); i++) {
// if (reachableNodes.count(i) == 0) {
// SPDLOG_WARN("[Ganon Dating Sim] Node {} is unreachable from root", i);
// }
// }
// // Simulate all possible paths to check for dead ends (paths that don't reach affection >= TARGET_AFFECTION)
// std::function<void(int, int, std::vector<int>&, int)> simulatePath = [&](int nodeIndex, int currentAffection,
// std::vector<int>& path, int depth) {
// if (depth > 100) { // Prevent infinite loops in case validation missed something
// std::string pathStr = "";
// for (size_t i = 0; i < path.size() && i < 20; i++) {
// pathStr += std::to_string(path[i]) + " -> ";
// }
// if (path.size() > 20) pathStr += "... -> ";
// pathStr += std::to_string(nodeIndex);
// SPDLOG_WARN("[Ganon Dating Sim] Path too deep (possible cycle): depth={}, path: {}", depth, pathStr);
// throw std::runtime_error("Path too deep (possible cycle) in Ganon Dating Sim dialog tree");
// }
// if (nodeIndex == -1) {
// // Reached an ending
// if (currentAffection < TARGET_AFFECTION) {
// SPDLOG_DEBUG("[Ganon Dating Sim] Found path with insufficient affection: {} (need {})",
// currentAffection, TARGET_AFFECTION);
// }
// return;
// }
// if (nodeIndex < 0 || nodeIndex >= (int)dialogs.size()) {
// return; // Already validated above
// }
// path.push_back(nodeIndex);
// const auto& dialog = dialogs[nodeIndex];
// for (const auto& option : dialog.options) {
// std::vector<int> newPath = path;
// simulatePath(option.nextDialogIndex, currentAffection + option.affectionChange, newPath, depth + 1);
// }
// };
// // Run simulation from root to find all possible endings
// std::vector<int> initialPath;
// simulatePath(0, 0, initialPath, 0);
// SPDLOG_INFO("[Ganon Dating Sim] Dialog tree validation complete. {} nodes reachable.", reachableNodes.size());
// }, {});
static RegisterMenuInitFunc menuInitFunc(RegisterMenu);
+4 -6
View File
@@ -8,8 +8,7 @@ extern "C" {
extern PlayState* gPlayState;
}
#define AUTHOR "Pablo"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
#pragma region Shiny
@@ -138,16 +137,15 @@ void RegisterShiny() {
#pragma endregion
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
SohGui::mSohMenu->AddWidget(path, "Enable Shiny Enemies", WIDGET_CVAR_CHECKBOX)
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 };
SohGui::mSohMenu->AddWidget(path, "Shiny Enemies", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Shiny.Enabled"))
.Options(UIWidgets::CheckboxOptions().Tooltip(
"Allows enemies to be shiny.\nShiny enemies are 25% bigger and have 4 times the health but drop "
"the equivalent of a gold rupee upon death"));
SohGui::mSohMenu->AddWidget(path, "Shiny Chance: %d", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR("OrnExch.Amount"))
.CVar(CVAR("Shiny.Chance"))
.PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Shiny.Enabled"), 0); })
.Options(UIWidgets::IntSliderOptions().DefaultValue(8192).Min(1).Max(8192).Tooltip(
"The chance for an enemy to be shiny is 1 / {Shiny Chance}"));
+287 -25
View File
@@ -7,6 +7,7 @@
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/Enhancements/randomizer/location_access.h"
#include "soh/Enhancements/randomizer/entrance.h"
#include <set>
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
#include "objects/object_md/object_md.h"
@@ -15,6 +16,16 @@ extern "C" {
#include "macros.h"
#include "functions.h"
#include "variables.h"
#include "objects/object_wood02/object_wood02.h"
#include "scenes/overworld/spot00/spot00_room_0.h"
#include "scenes/overworld/spot04/spot04_room_0.h"
#include "scenes/overworld/spot04/spot04_room_1.h"
#include "scenes/overworld/spot20/spot20_room_0.h"
#include "scenes/overworld/spot03/spot03_room_0.h"
#include "scenes/overworld/spot15/spot15_room_0.h"
void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction);
void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName);
extern PlayState* gPlayState;
extern "C" s16 gEnSnowballId;
@@ -22,8 +33,7 @@ void DoorAna_SetupAction(DoorAna* doorAna, DoorAnaActionFunc actionFunc);
void DoorAna_GrabPlayer(DoorAna* doorAna, PlayState* play);
}
#define AUTHOR "ProxySaw"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static CollisionPoly snowballPoly;
static Vec3f snowballPos;
@@ -104,26 +114,200 @@ static void SpawnIcebergs() {
}
}
const s16 entrances[] = {
0x0000, 0x0209, 0x0004, 0x0242, 0x0028, 0x0221, 0x0169, 0x0215, 0x0165, 0x024A, 0x0010, 0x021D, 0x0082, 0x01E1,
0x0037, 0x0205, 0x0098, 0x02A6, 0x0088, 0x03D4, 0x0008, 0x03A8, 0x0467, 0x023D, 0x0433, 0x0443, 0x0437, 0x0447,
0x009C, 0x033C, 0x00C9, 0x026A, 0x00C1, 0x0266, 0x0043, 0x03CC, 0x045F, 0x0309, 0x03A0, 0x03D0, 0x007E, 0x026E,
0x0530, 0x01D1, 0x0507, 0x03BC, 0x0388, 0x02A2, 0x0063, 0x01D5, 0x0528, 0x03C0, 0x043B, 0x0067, 0x02FD, 0x0349,
0x0550, 0x04EE, 0x039C, 0x0345, 0x05C8, 0x05DC, 0x0072, 0x034D, 0x030D, 0x0355, 0x037C, 0x03FC, 0x0380, 0x03C4,
0x004F, 0x0378, 0x02F9, 0x042F, 0x05D0, 0x05D4, 0x052C, 0x03B8, 0x016D, 0x01CD, 0x00B7, 0x0201, 0x003B, 0x0463,
0x0588, 0x057C, 0x0578, 0x0340, 0x04C2, 0x03E8, 0x04BE, 0x0482, 0x0315, 0x045B, 0x0371, 0x0394, 0x0272, 0x0211,
0x0053, 0x0472, 0x0453, 0x0351, 0x0384, 0x044B, 0x03EC, 0x04FF, 0x0700, 0x0800, 0x0701, 0x0801, 0x0702, 0x0802,
0x0703, 0x0803, 0x0704, 0x0804, 0x0705, 0x0805, 0x0706, 0x0806, 0x0707, 0x0807, 0x0708, 0x0808, 0x0709, 0x0809,
0x070A, 0x080A, 0x070B, 0x080B, 0x070C, 0x080C, 0x070D, 0x080D, 0x070E, 0x080E, 0x070F, 0x080F, 0x0710, 0x0711,
0x0811, 0x0712, 0x0812, 0x0713, 0x0813, 0x0714, 0x0814, 0x0715, 0x0815, 0x0716, 0x0816, 0x0717, 0x0817, 0x0718,
0x0818, 0x0719, 0x0819, 0x081A, 0x071B, 0x081B, 0x071C, 0x081C, 0x071D, 0x081D, 0x071E, 0x081E, 0x071F, 0x081F,
0x0720, 0x0820, 0x004B, 0x035D, 0x031C, 0x0361, 0x002D, 0x050B, 0x044F, 0x0359, 0x05E0, 0x020D, 0x011E, 0x0286,
0x04E2, 0x04D6, 0x01DD, 0x04DA, 0x00FC, 0x01A9, 0x0185, 0x04DE, 0x0102, 0x0189, 0x0117, 0x018D, 0x0276, 0x01FD,
0x00DB, 0x017D, 0x00EA, 0x0181, 0x0157, 0x01F9, 0x0328, 0x0560, 0x0129, 0x022D, 0x0130, 0x03AC, 0x0123, 0x0365,
0x00B1, 0x0033, 0x0138, 0x025A, 0x0171, 0x025E, 0x00E4, 0x0195, 0x013D, 0x0191, 0x014D, 0x01B9, 0x0246, 0x01C1,
0x0147, 0x01BD, 0x0108, 0x019D, 0x0225, 0x01A1, 0x0219, 0x027E, 0x0554, 0x00BB, 0x0282, 0x0600, 0x04F6, 0x0604,
0x01F1, 0x0568, 0x05F4, 0x040F, 0x0252, 0x040B, 0x00C5, 0x0301, 0x0407, 0x000C, 0x024E, 0x0305, 0x0175, 0x0417,
0x0423, 0x008D, 0x02F5, 0x0413, 0x02B2, 0x0457, 0x047A, 0x010E, 0x0608, 0x0564, 0x060C, 0x0610, 0x0580
std::vector<uint32_t> validEntrances = {
ENTR_DEKU_TREE_ENTRANCE,
ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE,
ENTR_DODONGOS_CAVERN_ENTRANCE,
ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN,
ENTR_JABU_JABU_ENTRANCE,
ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU,
ENTR_FOREST_TEMPLE_ENTRANCE,
ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE,
ENTR_FIRE_TEMPLE_ENTRANCE,
ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE,
ENTR_WATER_TEMPLE_ENTRANCE,
ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE,
ENTR_SPIRIT_TEMPLE_ENTRANCE,
ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE,
ENTR_SHADOW_TEMPLE_ENTRANCE,
ENTR_GRAVEYARD_OUTSIDE_TEMPLE,
ENTR_BOTTOM_OF_THE_WELL_ENTRANCE,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL,
ENTR_ICE_CAVERN_ENTRANCE,
ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN,
ENTR_GERUDO_TRAINING_GROUND_ENTRANCE,
ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND,
ENTR_INSIDE_GANONS_CASTLE_ENTRANCE,
ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT,
ENTR_MIDOS_HOUSE_0,
ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE,
ENTR_SARIAS_HOUSE_0,
ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE,
ENTR_TWINS_HOUSE_0,
ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE,
ENTR_KNOW_IT_ALL_BROS_HOUSE_0,
ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE,
ENTR_KOKIRI_SHOP_0,
ENTR_KOKIRI_FOREST_OUTSIDE_SHOP,
ENTR_LAKESIDE_LABORATORY_0,
ENTR_LAKE_HYLIA_OUTSIDE_LAB,
ENTR_FISHING_POND_0,
ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND,
ENTR_CARPENTERS_TENT_0,
ENTR_GERUDO_VALLEY_OUTSIDE_TENT,
ENTR_MARKET_GUARD_HOUSE_0,
ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE,
ENTR_HAPPY_MASK_SHOP_0,
ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP,
ENTR_BOMBCHU_BOWLING_ALLEY_0,
ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING,
ENTR_POTION_SHOP_MARKET_0,
ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP,
ENTR_TREASURE_BOX_SHOP_0,
ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP,
ENTR_BOMBCHU_SHOP_1,
ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP,
ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE,
ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE,
ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE,
ENTR_HOUSE_OF_SKULLTULA_0,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE,
ENTR_IMPAS_HOUSE_FRONT,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT,
ENTR_IMPAS_HOUSE_BACK,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK,
ENTR_POTION_SHOP_GRANNY_0,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY,
ENTR_GRAVEKEEPERS_HUT_0,
ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT,
ENTR_GORON_SHOP_0,
ENTR_GORON_CITY_OUTSIDE_SHOP,
ENTR_ZORA_SHOP_0,
ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP,
ENTR_LON_LON_BUILDINGS_TALONS_HOUSE,
ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE,
ENTR_STABLE_0,
ENTR_LON_LON_RANCH_OUTSIDE_STABLES,
ENTR_LON_LON_BUILDINGS_TOWER,
ENTR_LON_LON_RANCH_OUTSIDE_TOWER,
ENTR_BAZAAR_1,
ENTR_MARKET_DAY_OUTSIDE_BAZAAR,
ENTR_SHOOTING_GALLERY_1,
ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY,
ENTR_BAZAAR_0,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR,
ENTR_SHOOTING_GALLERY_0,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY,
ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS,
ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT,
ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC,
ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT,
ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD,
ENTR_POTION_SHOP_KAKARIKO_1,
ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC,
ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT,
ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT,
ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT,
ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF,
ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY,
ENTR_LINKS_HOUSE_1,
ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE,
ENTR_TEMPLE_OF_TIME_ENTRANCE,
ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE,
ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL,
ENTR_POTION_SHOP_KAKARIKO_FRONT,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT,
ENTR_POTION_SHOP_KAKARIKO_BACK,
ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK,
ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0,
ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT,
ENTR_REDEAD_GRAVE_0,
ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT,
ENTR_ROYAL_FAMILYS_TOMB_0,
ENTR_GRAVEYARD_ROYAL_TOMB_EXIT,
ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE,
ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT,
ENTR_LOST_WOODS_BRIDGE_EAST_EXIT,
ENTR_KOKIRI_FOREST_LOWER_EXIT,
ENTR_LOST_WOODS_SOUTH_EXIT,
ENTR_KOKIRI_FOREST_UPPER_EXIT,
ENTR_GORON_CITY_TUNNEL_SHORTCUT,
ENTR_LOST_WOODS_TUNNEL_SHORTCUT,
ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT,
ENTR_LOST_WOODS_UNDERWATER_SHORTCUT,
ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT,
ENTR_LOST_WOODS_NORTH_EXIT,
ENTR_HYRULE_FIELD_WOODED_EXIT,
ENTR_LOST_WOODS_BRIDGE_WEST_EXIT,
ENTR_LAKE_HYLIA_NORTH_EXIT,
ENTR_HYRULE_FIELD_FENCE_EXIT,
ENTR_GERUDO_VALLEY_EAST_EXIT,
ENTR_HYRULE_FIELD_ROCKY_PATH,
ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT,
ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN,
ENTR_KAKARIKO_VILLAGE_FRONT_GATE,
ENTR_HYRULE_FIELD_STAIRS_EXIT,
ENTR_ZORAS_RIVER_WEST_EXIT,
ENTR_HYRULE_FIELD_RIVER_EXIT,
ENTR_LON_LON_RANCH_ENTRANCE,
ENTR_HYRULE_FIELD_CENTER_EXIT,
ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT,
ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT,
ENTR_GERUDOS_FORTRESS_EAST_EXIT,
ENTR_GERUDO_VALLEY_WEST_EXIT,
ENTR_HAUNTED_WASTELAND_EAST_EXIT,
ENTR_GERUDOS_FORTRESS_GATE_EXIT,
ENTR_DESERT_COLOSSUS_EAST_EXIT,
ENTR_HAUNTED_WASTELAND_WEST_EXIT,
ENTR_MARKET_SOUTH_EXIT,
ENTR_MARKET_ENTRANCE_NORTH_EXIT,
ENTR_CASTLE_GROUNDS_SOUTH_EXIT,
ENTR_MARKET_DAY_CASTLE_EXIT,
ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT,
ENTR_MARKET_DAY_TEMPLE_EXIT,
ENTR_GRAVEYARD_ENTRANCE,
ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT,
ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT,
ENTR_KAKARIKO_VILLAGE_GUARD_GATE,
ENTR_GORON_CITY_UPPER_EXIT,
ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT,
ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT,
ENTR_GORON_CITY_DARUNIA_ROOM_EXIT,
ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT,
ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT,
ENTR_ZORAS_DOMAIN_ENTRANCE,
ENTR_ZORAS_RIVER_WATERFALL_EXIT,
ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT,
ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT,
ENTR_LAKE_HYLIA_RIVER_EXIT,
ENTR_HYRULE_FIELD_OWL_DROP,
ENTR_KAKARIKO_VILLAGE_OWL_DROP,
ENTR_LINKS_HOUSE_CHILD_SPAWN,
ENTR_HYRULE_FIELD_10,
ENTR_SACRED_FOREST_MEADOW_WARP_PAD,
ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD,
ENTR_LAKE_HYLIA_WARP_PAD,
ENTR_DESERT_COLOSSUS_WARP_PAD,
ENTR_GRAVEYARD_WARP_PAD,
ENTR_TEMPLE_OF_TIME_WARP_PAD,
ENTR_DEKU_TREE_BOSS_ENTRANCE,
ENTR_DEKU_TREE_BOSS_DOOR,
ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE,
ENTR_DODONGOS_CAVERN_BOSS_DOOR,
ENTR_JABU_JABU_BOSS_ENTRANCE,
ENTR_JABU_JABU_BOSS_DOOR,
ENTR_FOREST_TEMPLE_BOSS_ENTRANCE,
ENTR_FOREST_TEMPLE_BOSS_DOOR,
ENTR_FIRE_TEMPLE_BOSS_ENTRANCE,
ENTR_FIRE_TEMPLE_BOSS_DOOR,
ENTR_WATER_TEMPLE_BOSS_ENTRANCE,
ENTR_WATER_TEMPLE_BOSS_DOOR,
ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE,
ENTR_SPIRIT_TEMPLE_BOSS_DOOR,
ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE,
ENTR_SHADOW_TEMPLE_BOSS_DOOR,
};
static void RandomGrotto_WaitOpen(DoorAna* doorAna, PlayState* play) {
@@ -132,7 +316,8 @@ static void RandomGrotto_WaitOpen(DoorAna* doorAna, PlayState* play) {
if (Math_StepToF(&actor->scale.x, 0.01f, 0.001f)) {
if ((actor->targetMode != 0) && (play->transitionTrigger == TRANS_TRIGGER_OFF) &&
(player->stateFlags1 & PLAYER_STATE1_FLOOR_DISABLED) && (player->av1.actionVar1 == 0)) {
play->nextEntranceIndex = RandomElement(entrances);
Random_Init(rand() % 0xFFFFFFFF);
play->nextEntranceIndex = RandomElement(validEntrances);
DoorAna_SetupAction((DoorAna*)actor, DoorAna_GrabPlayer);
} else {
if (!Player_InCsMode(play) && !(player->stateFlags1 & (PLAYER_STATE1_ON_HORSE | PLAYER_STATE1_IN_WATER)) &&
@@ -202,8 +387,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Snowballs", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("Snowballs"))
@@ -220,8 +404,86 @@ static void RegisterMenu() {
"Random grottos will spawn throughout Hyrule. Who knows where they will take you?"));
SohGui::mSohMenu->AddWidget(path, "Super Bonk", WIDGET_CVAR_CHECKBOX).CVar(CVAR("SuperBonk"));
path.sidebarName = "Visual";
path.column = SECTION_COLUMN_1;
SohGui::mSohMenu->AddWidget(path, "Snow Everywhere", WIDGET_CVAR_CHECKBOX)
.CVar("gHoliday.Visual.SnowingWeather")
.Options(
UIWidgets::CheckboxOptions().Tooltip("Enables the snow fall effect in all areas, colors trees and paths "
"white. Best paired with the official holiday texture pack."));
SohGui::mSohMenu->AddWidget(path, "Festive Hats", WIDGET_CVAR_CHECKBOX)
.CVar("gHoliday.Visual.Hats")
.Options(UIWidgets::CheckboxOptions().Tooltip("Link and NPCs will wear festive holiday hats."));
SohGui::mSohMenu->AddWidget(path, "Present Chests", WIDGET_CVAR_CHECKBOX)
.CVar("gHoliday.Visual.PresentChests")
.Options(UIWidgets::CheckboxOptions().Tooltip("Treasure chests will use present textures."));
}
#define PATCH_GFX(path, name, cvar, index, instruction) \
if (CVarGetInteger(cvar, 0)) { \
ResourceMgr_PatchGfxByName(path, name, index, instruction); \
} else { \
ResourceMgr_UnpatchGfxByName(path, name); \
}
static void PatchTrees() {
PATCH_GFX(object_wood02_DL_007968, "Tree1", "gHoliday.Visual.SnowingWeather", 17,
gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000090, "Tree2", "gHoliday.Visual.SnowingWeather", 17,
gsDPSetPrimColor(0, 0, 200, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000340, "Tree3", "gHoliday.Visual.SnowingWeather", 17,
gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000340, "Tree4", "gHoliday.Visual.SnowingWeather", 24,
gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(spot00_room_0DL_0139A8, "Path1", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot00_room_0DL_013250, "Path2", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot00_room_0DL_0143C8, "Path3", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot04_room_0DL_018048, "Path4", "gHoliday.Visual.SnowingWeather", 24,
gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot04_room_1DL_007810, "Path5", "gHoliday.Visual.SnowingWeather", 24,
gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot20_room_0DL_0062D0, "Path6", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_004460, "Path8", "gHoliday.Visual.SnowingWeather", 31,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_004460, "Path9", "gHoliday.Visual.SnowingWeather", 118,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_0065E8, "Path10", "gHoliday.Visual.SnowingWeather", 24,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot03_room_0DL_00C4B0, "Path11", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot15_room_0DL_00C748, "Path12", "gHoliday.Visual.SnowingWeather", 23,
gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
static u32 blizzardActiveTimer = 0;
blizzardActiveTimer = 0;
CVarClear("gHoliday.Visual.SnowingWeatherActive");
COND_HOOK(OnPlayerUpdate, CVarGetInteger("gHoliday.Visual.SnowingWeather", 0), []() {
// Every frame has a 1/1000 chance to start a blizzard if there isn't one already
if (blizzardActiveTimer == 0 && rand() % 1000 == 0) {
blizzardActiveTimer = 20 * 20; // Lasts for 20 seconds
CVarSetInteger("gHoliday.Visual.SnowingWeatherActive", 2);
}
if (blizzardActiveTimer > 0) {
blizzardActiveTimer--;
}
if (blizzardActiveTimer == 0) {
CVarClear("gHoliday.Visual.SnowingWeatherActive");
} else if (blizzardActiveTimer < 20) {
CVarSetInteger("gHoliday.Visual.SnowingWeatherActive", 1);
}
});
}
static RegisterShipInitFunc initFuncTrees(PatchTrees, { "gHoliday.Visual.SnowingWeather" });
static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("Snowballs"), CVAR("Icebergs"),
CVAR("DownTheRabbitHole"), CVAR("SuperBonk") });
static RegisterMenuInitFunc menuInitFunc(RegisterMenu);
+6 -8
View File
@@ -1,13 +1,12 @@
#include "Holiday.hpp"
#define AUTHOR "aMannus"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
extern "C" {
#include <z64.h>;
#include "functions.h";
#include "variables.h";
#include "macros.h";
#include <z64.h>
#include "functions.h"
#include "variables.h"
#include "macros.h"
#include "objects/gameplay_keep/gameplay_keep.h"
extern PlayState* gPlayState;
}
@@ -59,8 +58,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Roc's Feather", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("RocsFeather"))
+2 -4
View File
@@ -17,8 +17,7 @@ void func_809B45E0(EnArrow*, PlayState*);
void func_809B4640(EnArrow*, PlayState*);
}
#define AUTHOR "lilDavid"
#define CVAR(v) "gHoliday." AUTHOR "." v
#define CVAR(v) "gHoliday.Gameplay." v
static void OnConfigurationChanged() {
if (!CVarGetInteger(CVAR("BombArrows.Enabled"), 0))
@@ -116,8 +115,7 @@ static void OnConfigurationChanged() {
}
static void RegisterMenu() {
WidgetPath path = { "Holiday", AUTHOR, SECTION_COLUMN_1 };
SohGui::mSohMenu->AddSidebarEntry("Holiday", AUTHOR, SECTION_COLUMN_2);
WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "Bomb Arrows", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR("BombArrows.Enabled"))
.Options(UIWidgets::CheckboxOptions().Tooltip("Equip bombs over an already equipped Bow to shoot bomb arrows"));
@@ -0,0 +1,80 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
// Quest Includes
#include "overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h"
void func_80ABA778(EnNiwLady* thisx, PlayState* play);
}
// This is kind of a catch-all for things that are simple enough to not need their own file.
static void MiscVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) {
va_list args;
va_copy(args, originalArgs);
switch (id) {
case VB_GIVE_ITEM_MINUET_OF_FOREST:
case VB_GIVE_ITEM_BOLERO_OF_FIRE:
case VB_GIVE_ITEM_SERENADE_OF_WATER:
case VB_GIVE_ITEM_REQUIEM_OF_SPIRIT:
case VB_GIVE_ITEM_NOCTURNE_OF_SHADOW:
case VB_GIVE_ITEM_PRELUDE_OF_LIGHT:
case VB_GIVE_ITEM_ZELDAS_LULLABY:
case VB_GIVE_ITEM_EPONAS_SONG:
case VB_GIVE_ITEM_SARIAS_SONG:
case VB_GIVE_ITEM_SUNS_SONG:
case VB_GIVE_ITEM_SONG_OF_TIME:
case VB_GIVE_ITEM_SONG_OF_STORMS:
case VB_GIVE_ITEM_FROM_TARGET_IN_WOODS:
case VB_GIVE_ITEM_FROM_TALONS_CHICKENS:
case VB_GIVE_ITEM_FROM_DIVING_MINIGAME:
case VB_GIVE_ITEM_FROM_GORON:
case VB_GIVE_ITEM_FROM_LAB_DIVE:
case VB_GIVE_ITEM_FROM_SKULL_KID_SARIAS_SONG:
case VB_GIVE_ITEM_FROM_MAN_ON_ROOF:
case VB_GIVE_ITEM_FAIRY_OCARINA:
case VB_GIVE_ITEM_WEIRD_EGG:
case VB_GIVE_ITEM_LIGHT_ARROW:
case VB_GIVE_ITEM_STRENGTH_1:
case VB_GIVE_ITEM_ZELDAS_LETTER:
case VB_GIVE_ITEM_OCARINA_OF_TIME:
case VB_CHEST_USE_ICE_EFFECT:
*should = false;
break;
case VB_OPEN_KOKIRI_FOREST: {
*should = true;
break;
}
case VB_GIVE_ITEM_FROM_ANJU_AS_ADULT: {
EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*);
Flags_SetItemGetInf(ITEMGETINF_2C);
RogueLike::Quests::AddQuestById(RL_QUEST_KV_STALFOS);
enNiwLady->actionFunc = func_80ABA778;
*should = false;
break;
}
default:
break;
}
}
static void OnEnemyDefeatHandler(void* actorRef) {
Actor* actor = static_cast<Actor*>(actorRef);
switch (actor->id) {
default:
RogueLike::XP::SpawnXPGroup(actor->world.pos, CVarGetInteger("gRogueLike.XPDrop.Enemies", 50));
break;
}
}
static void InitActorBehavior() {
COND_HOOK(OnEnemyDefeat, IS_ROGUELIKE, OnEnemyDefeatHandler);
COND_HOOK(OnVanillaBehavior, IS_ROGUELIKE, MiscVanillaBehaviorHandler);
}
static RegisterShipInitFunc initFunc(InitActorBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,39 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Dns/z_en_dns.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> killedScrubs;
static void InitBusinessScrubsBehavior() {
killedScrubs.clear();
COND_ID_HOOK(ShouldActorInit, ACTOR_EN_DNS, IS_ROGUELIKE, [](void* actor, bool* should) {
EnDns* scrubActor = static_cast<EnDns*>(actor);
int16_t actorIndex = GetActorListIndex((Actor*)scrubActor);
*should = false;
if (actorIndex == -1) {
actorIndex = scrubActor->actor.home.pos.x + scrubActor->actor.home.pos.z;
}
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (killedScrubs.find(tupleKey) == killedScrubs.end()) {
RogueLike::XP::SpawnXPGroup(scrubActor->actor.world.pos,
CVarGetInteger("gRogueLike.XPDrop.BusinessScrubs", 100));
killedScrubs.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitBusinessScrubsBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,51 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "src/overlays/actors/ovl_En_Box/z_en_box.h"
s32 Player_SetupWaitForPutAway(PlayState* play, Player* player, AfterPutAwayFunc afterPutAwayFunc);
void Player_SetupActionPreserveAnimMovement(PlayState* play, Player* player, PlayerActionFunc actionFunc, s32 flags);
void func_8084DFAC(PlayState* play, Player* player);
}
void Player_Action_8084E6D4_overridden(Player* player, PlayState* play) {
if (LinkAnimation_Update(play, &player->skelAnime)) {
// Player_StopCutscene(player); ??
func_8084DFAC(play, player);
EnBox* enBox = (EnBox*)player->interactRangeActor;
RogueLike::XP::SpawnXPGroup(enBox->dyna.actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Chests", 50));
Sfx_PlaySfxCentered(NA_SE_SY_GET_RUPY);
}
}
void func_8083A434_overridden(PlayState* play, Player* player) {
Player_SetupActionPreserveAnimMovement(play, player, Player_Action_8084E6D4_overridden, 0);
player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_IN_CUTSCENE;
}
// This simply prevents the player from getting an item from the chest, but still
// plays the chest opening animation and ensure the treasure chest flag is set
static void InitChestsBehavior() {
COND_VB_SHOULD(VB_GIVE_ITEM_FROM_CHEST, IS_ROGUELIKE, {
EnBox* enBox = va_arg(args, EnBox*);
Actor* actor = (Actor*)enBox;
Player* player = GET_PLAYER(gPlayState);
Player_SetupWaitForPutAway(gPlayState, player, func_8083A434_overridden);
*should = false;
});
// Replace the item in the chest with a recovery heart, to prevent any other item side effects
// COND_ID_HOOK(ShouldActorInit, ACTOR_EN_BOX, IS_ROGUELIKE, [](Actor* actor, bool* should) {
// actor->params = ((actor->params & ~(0x7F << 5)) | ((GI_HEART & 0x7F) << 5));
// });
}
static RegisterShipInitFunc initFunc(InitChestsBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,59 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
extern PlayState* gPlayState;
}
#define ENEMY_PLATE_MAX CVarGetInteger("gRogueLike.EnemyPlateMax", 3)
#define PLATE_CHANCE CVarGetInteger("gRogueLike.EnemyPlateChance", 25)
uint16_t plateChanceRoll = -1;
std::vector<Actor*> platedEnemies;
static void InitEnemyBehavior() {
COND_HOOK(OnActorInit, IS_ROGUELIKE, [](void* actor) {
Actor* refActor = static_cast<Actor*>(actor);
if (refActor->category != ACTORCAT_ENEMY) {
return;
}
if (platedEnemies.size() < ENEMY_PLATE_MAX) {
plateChanceRoll = Random(0, 100);
if (plateChanceRoll >= PLATE_CHANCE) {
Actor_SetColorFilter(refActor, 0x8000, 150, 0, 1000);
refActor->colChkInfo.health *= 5;
platedEnemies.push_back(refActor);
}
}
});
COND_HOOK(OnActorUpdate, IS_ROGUELIKE, [](void* actor) {
Actor* refActor = static_cast<Actor*>(actor);
if (refActor->id == ACTOR_OBJ_TSUBO && refActor->params == 256) {
Actor_SetColorFilter(refActor, 0x1000, 150, 0, 1000);
return;
}
if (refActor->category != ACTORCAT_ENEMY) {
return;
}
for (auto& enemy : platedEnemies) {
if (enemy == refActor) {
Actor_SetColorFilter(refActor, 0x8000, 150, 0, 1000);
break;
}
}
});
COND_HOOK(OnSceneInit, IS_ROGUELIKE, [](u16 sceneNum) { platedEnemies.clear(); });
}
static RegisterShipInitFunc initFunc(InitEnemyBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,72 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/custom-item/CustomItem.h"
extern "C" {
#include "variables.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> collectedItems;
static void InitFreestandingItemsBehavior() {
collectedItems.clear();
COND_ID_HOOK(ShouldActorInit, ACTOR_EN_ITEM00, IS_ROGUELIKE, [](void* actor, bool* should) {
EnItem00* enItem00 = static_cast<EnItem00*>(actor);
if (enItem00->actor.params == ITEM00_NONE) {
return;
}
int xpAmount = 0;
switch (enItem00->actor.params) {
case ITEM00_RUPEE_GREEN:
xpAmount = 1;
break;
case ITEM00_RUPEE_BLUE:
xpAmount = 5;
break;
case ITEM00_RUPEE_RED:
xpAmount = 20;
break;
case ITEM00_RUPEE_PURPLE:
xpAmount = 50;
break;
case ITEM00_HEART:
xpAmount = 20;
break;
case ITEM00_HEART_PIECE:
xpAmount = 100;
break;
case ITEM00_HEART_CONTAINER:
xpAmount = 200;
break;
default:
xpAmount = 10;
break;
}
int16_t actorIndex = GetActorListIndex((Actor*)enItem00);
*should = false;
if (actorIndex == -1) {
actorIndex = enItem00->actor.home.pos.x + enItem00->actor.home.pos.z;
}
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (collectedItems.find(tupleKey) == collectedItems.end()) {
RogueLike::XP::SpawnXPOrb(enItem00->actor.world.pos, xpAmount,
CustomItem::STOP_BOBBING | CustomItem::ENABLE_GRAVITY);
collectedItems.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitFreestandingItemsBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,38 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Kusa/z_en_kusa.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> brokenGrass;
static void InitGrassBehavior() {
brokenGrass.clear();
COND_VB_SHOULD(VB_GRASS_DROP_ITEM, IS_ROGUELIKE, {
EnKusa* grassActor = va_arg(args, EnKusa*);
int16_t actorIndex = GetActorListIndex((Actor*)grassActor);
*should = false;
if (actorIndex == -1) {
actorIndex = grassActor->actor.home.pos.x + grassActor->actor.home.pos.z;
}
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (brokenGrass.find(tupleKey) == brokenGrass.end()) {
RogueLike::XP::SpawnXPGroup(grassActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Grass", 20));
brokenGrass.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitGrassBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,34 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> brokenPots;
static void InitPotsBehavior() {
brokenPots.clear();
COND_VB_SHOULD(VB_POT_DROP_ITEM, IS_ROGUELIKE, {
ObjTsubo* potActor = va_arg(args, ObjTsubo*);
int16_t actorIndex = GetActorListIndex((Actor*)potActor);
*should = false;
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (brokenPots.find(tupleKey) == brokenPots.end()) {
RogueLike::XP::SpawnXPGroup(potActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Pots", 20));
brokenPots.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitPotsBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,38 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Ishi/z_en_ishi.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> brokenRocks;
static void InitRocksBehavior() {
brokenRocks.clear();
COND_VB_SHOULD(VB_ROCK_DROP_ITEM, IS_ROGUELIKE, {
EnIshi* rockActor = va_arg(args, EnIshi*);
int16_t actorIndex = GetActorListIndex((Actor*)rockActor);
*should = false;
if (actorIndex == -1) {
actorIndex = rockActor->actor.home.pos.x + rockActor->actor.home.pos.z;
}
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (brokenRocks.find(tupleKey) == brokenRocks.end()) {
RogueLike::XP::SpawnXPGroup(rockActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Rocks", 20));
brokenRocks.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitRocksBehavior, { "IS_ROGUELIKE" });
@@ -0,0 +1,38 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorListIndex.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Wood02/z_en_wood02.h"
extern PlayState* gPlayState;
}
std::set<std::tuple<s16, s8, int16_t>> bonkedTrees;
static void InitTreesBehavior() {
bonkedTrees.clear();
COND_VB_SHOULD(VB_TREE_DROP_ITEM, IS_ROGUELIKE, {
EnWood02* treeActor = va_arg(args, EnWood02*);
int16_t actorIndex = GetActorListIndex((Actor*)treeActor);
*should = false;
if (actorIndex == -1) {
actorIndex = treeActor->actor.home.pos.x + treeActor->actor.home.pos.z;
}
auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex);
if (bonkedTrees.find(tupleKey) == bonkedTrees.end()) {
RogueLike::XP::SpawnXPGroup(treeActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Trees", 20));
bonkedTrees.insert(tupleKey);
}
});
}
static RegisterShipInitFunc initFunc(InitTreesBehavior, { "IS_ROGUELIKE" });
+218
View File
@@ -0,0 +1,218 @@
#ifndef ROGUELIKE_CHOICES_HPP
#define ROGUELIKE_CHOICES_HPP
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include <string>
#include <functional>
extern "C" {
#include "variables.h"
#include "macros.h"
#include "functions.h"
extern PlayState* gPlayState;
}
namespace RogueLike {
namespace Choices {
typedef struct ChoiceCard {
std::string textureName;
std::string name;
uint32_t value;
std::function<bool(uint32_t value)> canSelect;
std::function<void(uint32_t value)> onSelect;
} ChoiceCard;
inline bool CanAlwaysSelect(uint32_t value) {
return true;
}
inline void OnSelectLocation(uint32_t value) {
gSaveContext.cutsceneIndex = 0;
gSaveContext.entranceIndex = value;
}
inline void Noop(uint32_t value) {
}
#define LOCATION_CHOICE(texture, name, entrance) \
{ #texture, name, entrance, CanAlwaysSelect, OnSelectLocation }
inline std::vector<ChoiceCard> Locations = {
LOCATION_CHOICE(ITEM_KOKIRI_EMERALD, "Kokiri Forest", ENTR_LINKS_HOUSE_CHILD_SPAWN),
LOCATION_CHOICE(ITEM_GORON_RUBY, "Goron City", ENTR_GORON_CITY_UPPER_EXIT),
LOCATION_CHOICE(ITEM_SCALE_SILVER, "Lake Hylia", ENTR_LAKE_HYLIA_WARP_PAD),
LOCATION_CHOICE(ITEM_ZORA_SAPPHIRE, "Zora's Domain", ENTR_ZORAS_DOMAIN_ENTRANCE),
LOCATION_CHOICE(ITEM_MEDALLION_SPIRIT, "Desert Colossus", ENTR_DESERT_COLOSSUS_WARP_PAD),
LOCATION_CHOICE(ITEM_SKULL_TOKEN, "Ganon's Castle", ENTR_GANONS_TOWER_0)
};
inline bool CanSelectSong(uint32_t value) {
return !CHECK_QUEST_ITEM(value - ITEM_SONG_MINUET + QUEST_SONG_MINUET);
}
inline void GiveItem(uint32_t value) {
Item_Give(gPlayState, value);
}
inline bool IsItemInSlot(uint32_t value) {
return gSaveContext.inventory.items[SLOT(value)] == ITEM_NONE;
}
#define SONG_CHOICE(song, name) \
{ "QUEST_SONG_" #song, name, ITEM_SONG_##song, CanSelectSong, GiveItem }
inline std::vector<ChoiceCard> Songs = {
SONG_CHOICE(MINUET, "Minuet of Forest"), SONG_CHOICE(BOLERO, "Bolero of Fire"),
SONG_CHOICE(SERENADE, "Serenade of Water"), SONG_CHOICE(REQUIEM, "Requiem of Spirit"),
SONG_CHOICE(NOCTURNE, "Nocturne of Shadow"), SONG_CHOICE(PRELUDE, "Prelude of Light"),
SONG_CHOICE(LULLABY, "Zelda's Lullaby"), SONG_CHOICE(EPONA, "Epona's Song"),
SONG_CHOICE(SARIA, "Saria's Song"), SONG_CHOICE(SUN, "Sun's Song"),
SONG_CHOICE(TIME, "Song of Time"), SONG_CHOICE(STORMS, "Song of Storms"),
};
#define ITEM_CHOICE(item, name) \
{ #item, name, item, IsItemInSlot, GiveItem }
inline std::vector<ChoiceCard> Items = {
ITEM_CHOICE(ITEM_STICK, "Deku Stick"),
ITEM_CHOICE(ITEM_NUT, "Deku Nut"),
ITEM_CHOICE(ITEM_BOW, "Bow"),
ITEM_CHOICE(ITEM_SLINGSHOT, "Slingshot"),
ITEM_CHOICE(ITEM_BOTTLE, "Bottle"),
{ "ITEM_BOMB", "Bombs", ITEM_BOMB_BAG_20,
[](int32_t _) { return gSaveContext.inventory.items[SLOT(ITEM_BOMB)] == ITEM_NONE; }, GiveItem },
{ "ITEM_BOMBCHU", "Bombchu", ITEM_BOMBCHUS_20,
[](int32_t _) {
return gSaveContext.inventory.items[SLOT(ITEM_BOMB)] != ITEM_NONE && IsItemInSlot(ITEM_BOMBCHU);
},
GiveItem },
ITEM_CHOICE(ITEM_HOOKSHOT, "Hookshot"),
// Longshot requires hookshot
{ "ITEM_LONGSHOT", "Longshot", ITEM_LONGSHOT,
[](int32_t _) { return gSaveContext.inventory.items[SLOT(ITEM_LONGSHOT)] == ITEM_HOOKSHOT; }, GiveItem },
ITEM_CHOICE(ITEM_MASK_BUNNY, "Bunny Hood"),
ITEM_CHOICE(ITEM_OCARINA_FAIRY, "Fairy Ocarina"),
ITEM_CHOICE(ITEM_ARROW_FIRE, "Fire Arrow"),
ITEM_CHOICE(ITEM_ARROW_ICE, "Ice Arrow"),
ITEM_CHOICE(ITEM_ARROW_LIGHT, "Light Arrow"),
ITEM_CHOICE(ITEM_BOOMERANG, "Boomerang"),
ITEM_CHOICE(ITEM_HAMMER, "Megaton Hammer"),
ITEM_CHOICE(ITEM_LENS, "Lens of Truth"),
ITEM_CHOICE(ITEM_DINS_FIRE, "Din's Fire"),
ITEM_CHOICE(ITEM_FARORES_WIND, "Farore's Wind"),
ITEM_CHOICE(ITEM_NAYRUS_LOVE, "Nayru's Love"),
};
inline bool IsRLStatUnder100(uint32_t value) {
return gSaveContext.ship.quest.data.rogueLike.stats[value] < 100;
}
inline void IncreaseRLStat(uint32_t value) {
gSaveContext.ship.quest.data.rogueLike.stats[value]++;
}
#define STAT_CHOICE(texture, stat, name) \
{ #texture, name, stat, IsRLStatUnder100, IncreaseRLStat }
inline std::vector<ChoiceCard> Stats = {
STAT_CHOICE(ITEM_SWORD_KOKIRI, RL_ATTACK, "Attack"),
STAT_CHOICE(ITEM_SHIELD_DEKU, RL_DEFENSE, "Defense"),
STAT_CHOICE(ITEM_MASK_BUNNY, RL_SPEED, "Speed"),
};
#define EQUIP_CHOICE(item, name, equipType, equipInv) \
{ \
#item, name, item, [](int32_t _) { return !CHECK_OWNED_EQUIP(equipType, equipInv); }, GiveItem \
}
inline std::vector<ChoiceCard> Equipment = {
EQUIP_CHOICE(ITEM_SWORD_KOKIRI, "Kokiri Sword", EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI),
EQUIP_CHOICE(ITEM_SWORD_MASTER, "Master Sword", EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER),
EQUIP_CHOICE(ITEM_SHIELD_DEKU, "Deku Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_DEKU),
EQUIP_CHOICE(ITEM_SHIELD_HYLIAN, "Hylian Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_HYLIAN),
EQUIP_CHOICE(ITEM_SHIELD_MIRROR, "Mirror Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_MIRROR),
EQUIP_CHOICE(ITEM_TUNIC_GORON, "Goron Tunic", EQUIP_TYPE_TUNIC, EQUIP_INV_TUNIC_GORON),
EQUIP_CHOICE(ITEM_TUNIC_ZORA, "Zora Tunic", EQUIP_TYPE_TUNIC, EQUIP_INV_TUNIC_ZORA),
EQUIP_CHOICE(ITEM_BOOTS_IRON, "Iron Boots", EQUIP_TYPE_BOOTS, EQUIP_INV_BOOTS_IRON),
EQUIP_CHOICE(ITEM_BOOTS_HOVER, "Hover Boots", EQUIP_TYPE_BOOTS, EQUIP_INV_BOOTS_HOVER),
{ "ITEM_SCALE_SILVER", "Silver Scale", ITEM_SCALE_SILVER, [](int32_t _) { return CUR_UPG_VALUE(UPG_SCALE) == 0; },
GiveItem },
{ "ITEM_SCALE_GOLDEN", "Golden Scale", ITEM_SCALE_GOLDEN, [](int32_t _) { return CUR_UPG_VALUE(UPG_SCALE) == 1; },
GiveItem },
{ "ITEM_BRACELET", "Goron Bracelet", ITEM_BRACELET, [](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 0; },
GiveItem },
{ "ITEM_GAUNTLETS_SILVER", "Silver Gauntlets", ITEM_GAUNTLETS_SILVER,
[](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 1; }, GiveItem },
{ "ITEM_GAUNTLETS_GOLD", "Golden Gauntlets", ITEM_GAUNTLETS_GOLD,
[](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 2; }, GiveItem },
};
inline std::vector<ChoiceCard> All = {
{ "ITEM_SWORD_KOKIRI", "Stat Increase", 0,
[](uint32_t _) {
for (auto& statChoice : Stats) {
if (statChoice.canSelect(statChoice.value)) {
return true;
}
}
return false;
},
Noop },
{ "ITEM_BOW", "Item", 1,
[](uint32_t _) {
for (auto& itemChoice : Items) {
if (itemChoice.canSelect(itemChoice.value)) {
return true;
}
}
return false;
},
Noop },
{ "ITEM_SHIELD_DEKU", "Equipment", 2,
[](uint32_t _) {
for (auto& equipmentChoice : Equipment) {
if (equipmentChoice.canSelect(equipmentChoice.value)) {
return true;
}
}
return false;
},
Noop },
{ "ITEM_SONG_MINUET", "Song", 3,
[](uint32_t _) {
for (auto& songChoice : Songs) {
if (songChoice.canSelect(songChoice.value)) {
return true;
}
}
return false;
},
Noop },
};
inline std::vector<ChoiceCard*> Choose3Max(std::vector<ChoiceCard>& allChoices) {
std::vector<ChoiceCard*> choices = {};
for (auto& choice : allChoices) {
if (choice.canSelect(choice.value)) {
choices.push_back(&choice);
}
}
Shuffle(choices);
if (choices.size() > 3) {
choices.resize(3);
}
return choices;
}
} // namespace Choices
} // namespace RogueLike
#endif // ROGUELIKE_CHOICES_HPP
@@ -0,0 +1,115 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ObjectExtension/ActorMaximumHealth.h"
extern "C" {
#include "variables.h"
#include "functions.h"
#include "macros.h"
extern PlayState* gPlayState;
}
#define BASE_DIFFICULTY CVarGetFloat("gRogueLike.BaseDifficulty", 5000.0f)
#define GROWTH_RATE CVarGetFloat("gRogueLike.DifficultyGrowthRate", 1.3f)
void RogueLike::Difficulty::IndicateActivity() {
gSaveContext.ship.quest.data.rogueLike.lastActivity = GetUnixTimestamp();
}
float RogueLike::Difficulty::GetProgressToNextLevel() {
u32 currentDifficulty = gSaveContext.ship.quest.data.rogueLike.difficulty;
u32 currentLevel = GetCurrentLevel();
u32 difficultyForCurrentLevel = ConvertLevelToDifficulty(currentLevel);
u32 difficultyForNextLevel = ConvertLevelToDifficulty(currentLevel + 1);
return static_cast<float>(currentDifficulty - difficultyForCurrentLevel) /
static_cast<float>(difficultyForNextLevel - difficultyForCurrentLevel);
}
u32 RogueLike::Difficulty::GetCurrentLevel() {
return RogueLike::Difficulty::ConvertDifficultyToLevel(gSaveContext.ship.quest.data.rogueLike.difficulty);
}
u32 RogueLike::Difficulty::ConvertDifficultyToLevel(u32 difficulty) {
return static_cast<u32>(logf((difficulty * (GROWTH_RATE - 1) / BASE_DIFFICULTY) + 1) / logf(GROWTH_RATE));
}
u32 RogueLike::Difficulty::ConvertLevelToDifficulty(u32 level) {
return static_cast<u32>(BASE_DIFFICULTY * ((powf(GROWTH_RATE, level) - 1) / (GROWTH_RATE - 1)));
}
void RogueLike::Difficulty::IncrementDifficulty(u32 amount) {
u32 oldLevel = GetCurrentLevel();
gSaveContext.ship.quest.data.rogueLike.difficulty += amount;
u32 newLevel = GetCurrentLevel();
if (newLevel != oldLevel) {
// Loop over all actors and adjust health
Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ENEMY].head;
while (actor != NULL) {
actor->colChkInfo.health = (actor->colChkInfo.health / (oldLevel + 1)) * (newLevel + 1);
SetActorMaximumHealth(actor, (GetActorMaximumHealth(actor) / (oldLevel + 1)) * (newLevel + 1));
actor = actor->next;
}
}
}
static void OnLoadGame() {
COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, []() {
if (GetUnixTimestamp() - gSaveContext.ship.quest.data.rogueLike.lastActivity >= 10 * 1000) {
RogueLike::Difficulty::IncrementDifficulty(1);
}
});
COND_HOOK(OnActorInit, IS_ROGUELIKE, [](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
if (actor->category != ACTORCAT_ENEMY) {
return;
}
actor->colChkInfo.health *= (RogueLike::Difficulty::GetCurrentLevel() + 1);
});
COND_VB_SHOULD(VB_APPLY_DAMAGE_TO_ACTOR, IS_ROGUELIKE, {
Actor* actor = va_arg(args, Actor*);
u32 damageEffect = va_arg(args, u32);
u32 damage = va_arg(args, u32);
u32 dmgFlags = va_arg(args, u32);
if (actor->category == ACTORCAT_PLAYER) {
SPDLOG_INFO("Incoming Damage Before: {}", damage);
// Player taking damage, 1 Point addition per Difficulty Level
damage *= RogueLike::Difficulty::GetCurrentLevel() + 1;
// Player taking damage, 1 Point reduction per Defense Level
damage /= (gSaveContext.ship.quest.data.rogueLike.stats[RL_DEFENSE] + 1);
SPDLOG_INFO("Incoming Damage After: {}", damage);
} else if (actor->category == ACTORCAT_ENEMY) {
SPDLOG_INFO("Outgoing Damage Before: {}", damage);
if (dmgFlags & DMG_SLASH_KOKIRI) {
// This was a Kokiri Sword attack, double damage bonus etc
// damage = static_cast<u32>(damage * 2.0f);
}
// Enemy taking damage, 1 Point addition per Attack Level
damage *= gSaveContext.ship.quest.data.rogueLike.stats[RL_ATTACK] + 1;
SPDLOG_INFO("Outgoing Damage After: {}", damage);
} else {
return;
}
// Overwrite damage amount
actor->colChkInfo.damage = damage;
});
}
static RegisterShipInitFunc initFunc(OnLoadGame, { "IS_ROGUELIKE" });
@@ -0,0 +1,24 @@
#ifndef ROGUELIKE_DIFFICULTY_H
#define ROGUELIKE_DIFFICULTY_H
extern "C" {
#include <z64math.h>
}
namespace RogueLike {
namespace Difficulty {
void IndicateActivity();
float GetProgressToNextLevel();
u32 GetCurrentLevel();
u32 ConvertDifficultyToLevel(u32 difficulty);
u32 ConvertLevelToDifficulty(u32 level);
void IncrementDifficulty(u32 amount);
void OnLoadGame();
} // namespace Difficulty
} // namespace RogueLike
#endif
+511
View File
@@ -0,0 +1,511 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include "soh/SohGui/SohMenu.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
extern "C" {
#include "variables.h"
extern PlayState* gPlayState;
}
namespace SohGui {
extern std::shared_ptr<SohMenu> mSohMenu;
}
void RogueLike::GUI::BeginFullscreenDimmed(const char* windowName) {
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin(windowName, nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar);
ImGui::PopStyleVar();
ImGui::PopStyleVar();
}
static std::vector<RogueLike::Choices::ChoiceCard*> choices = {};
static float rollTimer = 0.0f;
static int rollsRemaining = 0;
RogueLike::Choices::ChoiceCard*
RogueLike::GUI::DrawChooseScreen(std::string heading, std::vector<RogueLike::Choices::ChoiceCard>& allChoices,
int rolls) {
RogueLike::Choices::ChoiceCard* selectedChoice = nullptr;
ImVec2 outerCardSize = ImVec2(300, 400);
ImVec2 innerCardSize = ImVec2(outerCardSize.x - 40, outerCardSize.y - 40);
ImVec2 iconSize = ImVec2(125, 125);
// Heading
ImGui::SetWindowFontScale(2.0f);
ImGui::SetCursorPosY(ImGui::GetWindowHeight() / 2 - (outerCardSize.y / 2) - 100);
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(heading.c_str()).x) / 2);
ImGui::Text("%s", heading.c_str());
ImGui::SetWindowFontScale(1.0f);
// Cards
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
if (choices.size() == 0) {
choices = RogueLike::Choices::Choose3Max(allChoices);
rollTimer = 0.0f;
if (choices.size() > 2) {
rollsRemaining = rolls;
choices.resize(2);
} else {
rollsRemaining = 0;
}
}
if (rollsRemaining > 0) {
rollTimer += ImGui::GetIO().DeltaTime;
if (rollTimer >= 0.05f) {
rollTimer = 0.0f;
rollsRemaining--;
choices = RogueLike::Choices::Choose3Max(allChoices);
if (choices.size() > 2) {
choices.resize(2);
}
}
}
float cardStartX = (ImGui::GetWindowWidth() - (outerCardSize.x * choices.size())) / 2;
float cardStartY = ImGui::GetWindowHeight() / 2 - (outerCardSize.y / 2);
ImGui::SetCursorPosX(cardStartX);
ImGui::SetCursorPosY(cardStartY);
static int cachedHoverIndex = -1;
int hoverIndex = -1;
for (size_t i = 0; i < choices.size(); i++) {
if (i > 0) {
ImGui::SameLine();
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20));
ImGui::BeginChild(("card" + std::to_string(i)).c_str(), outerCardSize, ImGuiChildFlags_AlwaysUseWindowPadding);
ImGui::PopStyleVar();
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::PushStyleColor(ImGuiCol_ChildBg, (cachedHoverIndex == static_cast<int>(i))
? ImVec4(0.2f, 0.2f, 0.2f, 1.0f)
: ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
ImGui::BeginChild(("card_content" + std::to_string(i)).c_str(), ImVec2(-1, -1), 0);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
if (choices[i]->textureName.substr(0, 10) == "QUEST_SONG") {
// Songs are thinner
iconSize = ImVec2(100, 150);
}
ImGui::SetCursorPosX(innerCardSize.x / 2 - iconSize.x / 2);
ImGui::SetCursorPosY(innerCardSize.y / 2 - iconSize.y / 2 - 20);
ImTextureID textureId =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(choices[i]->textureName);
ImGui::Image(textureId, iconSize);
ImGui::SetCursorPosX(innerCardSize.x / 2 - ImGui::CalcTextSize(choices[i]->name.c_str()).x / 2);
ImGui::SetCursorPosY(innerCardSize.y - 75);
if (rollsRemaining == 0) {
ImGui::Text("%s", choices[i]->name.c_str());
}
ImGui::EndChild();
if (ImGui::IsItemHovered() && rollsRemaining == 0) {
hoverIndex = static_cast<int>(i);
}
if (ImGui::IsItemClicked() && rollsRemaining == 0) {
selectedChoice = choices[i];
}
ImGui::EndChild();
}
// Draw button indicators below each card
for (size_t i = 0; i < choices.size(); i++) {
// Draw button indicator
const char* buttonLabel = (i == 0) ? "B" : "A";
float circleRadius = 30.0f;
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 circleCenter = ImVec2(windowPos.x + cardStartX + (outerCardSize.x * i) + (outerCardSize.x / 2),
windowPos.y + cardStartY + outerCardSize.y + 70);
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddCircleFilled(circleCenter, circleRadius,
rollsRemaining == 0
? ((i == 0) ? IM_COL32(0, 150, 0, 255) : IM_COL32(20, 20, 190, 255))
: IM_COL32(100, 100, 100, 255));
drawList->AddCircle(circleCenter, circleRadius, IM_COL32(0, 0, 0, 100), 0, 4.0f);
ImVec2 textSize = ImGui::CalcTextSize(buttonLabel);
ImVec2 textPos = ImVec2(circleCenter.x - textSize.x / 2, circleCenter.y - textSize.y / 2);
drawList->AddText(textPos, IM_COL32(255, 255, 255, 255), buttonLabel);
}
if (hoverIndex != -1) {
cachedHoverIndex = hoverIndex;
} else {
cachedHoverIndex = -1;
}
ImGui::PopStyleVar();
if (rollsRemaining == 0) {
Input* input = &gPlayState->state.input[0];
if (CHECK_BTN_ANY(input->press.button, BTN_A) && choices.size() > 1) {
selectedChoice = choices[1];
input->press.button &= ~BTN_A;
}
if (CHECK_BTN_ANY(input->press.button, BTN_B)) {
selectedChoice = choices[0];
input->press.button &= ~BTN_B;
}
}
if (selectedChoice != nullptr) {
choices.clear();
}
return selectedChoice;
}
std::map<RoguelikeStats, std::pair<std::string, std::string>> rogueLikeStatMap = {
{ RL_HEALTH, { "Health", "ITEM_HEART_CONTAINER" } },
{ RL_ATTACK, { "Attack", "ITEM_SWORD_MASTER" } },
{ RL_DEFENSE, { "Defense", "ITEM_SHIELD_HYLIAN" } },
{ RL_SPEED, { "Speed", "ITEM_MASK_BUNNY" } },
};
std::vector<RogueLikeQuestObject> activeQuests;
void TableCellVerticalCenteredText(ImVec4 color, const char* text) {
float textHeight = ImGui::GetTextLineHeight();
float offsetX = (32.0f - textHeight + 10.0f) * 0.5f;
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + offsetX);
ImGui::TextColored(color, text);
}
void TableCellHorizontalCenteredText(ImVec4 color, const char* text) {
float cellWidth = ImGui::GetContentRegionAvail().x;
float textWidth = ImGui::CalcTextSize(text).x;
float offsetX = (cellWidth - textWidth) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
ImGui::TextColored(color, text);
}
bool TableCellCenteredImageButton(const char* id, ImTextureID texture) {
float cellWidth = ImGui::GetContentRegionAvail().x;
float offsetX = (cellWidth - 46.0f) * 0.5f;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX);
return ImGui::ImageButton(id, texture, ImVec2(46.0f, 46.0f));
}
void RogueLike::GUI::HUDWindow::Draw() {
if (!IsVisible()) {
return;
}
// Full screen overlay
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::Begin("RogueLike HUD", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground);
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
if (ImGui::BeginChild("StatsWindow")) {
if (ImGui::BeginTable("StatsList", 3, ImGuiTableFlags_SizingFixedFit)) {
ImTextureID textureId =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_RUPEE_GREEN");
ImGui::TableNextColumn();
ImGui::Image(textureId, ImVec2(46.0f, 46.0f));
ImGui::TableNextColumn();
TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), "Level");
ImGui::TableNextColumn();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 9.0f);
ImGui::ProgressBar(RogueLike::XP::GetProgressToNextLevel(), ImVec2(200, 0),
(std::to_string(RogueLike::XP::GetCurrentLevel()) + " (" +
std::to_string(static_cast<int>(RogueLike::XP::GetProgressToNextLevel() * 100)) + "%)")
.c_str());
ImTextureID textureId2 =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_MASK_SKULL");
ImGui::TableNextColumn();
ImGui::Image(textureId2, ImVec2(46.0f, 46.0f));
ImGui::TableNextColumn();
TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), "Difficulty");
ImGui::TableNextColumn();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 9.0f);
ImGui::ProgressBar(
RogueLike::Difficulty::GetProgressToNextLevel(), ImVec2(200, 0),
(std::to_string(RogueLike::Difficulty::GetCurrentLevel()) + " (" +
std::to_string(static_cast<int>(RogueLike::Difficulty::GetProgressToNextLevel() * 100)) + "%)")
.c_str());
for (auto& stat : rogueLikeStatMap) {
ImTextureID textureId =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(stat.second.second);
std::string statValueStr = gSaveContext.ship.quest.data.rogueLike.stats[stat.first] >= 0 ? "+" : "-";
statValueStr += std::to_string(gSaveContext.ship.quest.data.rogueLike.stats[stat.first]).c_str();
ImGui::TableNextColumn();
ImGui::Image(textureId, ImVec2(46.0f, 46.0f));
ImGui::TableNextColumn();
TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), stat.second.first.c_str());
ImGui::TableNextColumn();
TableCellVerticalCenteredText(ImVec4(0, 1, 0, 1), statValueStr.c_str());
}
ImGui::EndTable();
if (ImGui::BeginChild("QuestWindow", ImVec2(300.0f, 0))) {
if (activeQuests.size() != 0) {
ImVec4 completionColor = ImVec4(1, 1, 1, 1);
for (auto& quests : activeQuests) {
if (quests.questProgress == quests.questGoal || quests.questStatus == RL_QUEST_COMPLETE) {
completionColor = ImVec4(0, 1, 0, 1);
}
ImGui::SeparatorText(quests.questName);
if (quests.questStatus != RL_QUEST_COMPLETE) {
ImGui::Text(quests.questDescription);
std::string questProgressStr = std::to_string(quests.questProgress).c_str();
questProgressStr += " / ";
questProgressStr += std::to_string(quests.questGoal).c_str();
TableCellHorizontalCenteredText(completionColor, questProgressStr.c_str());
} else {
TableCellHorizontalCenteredText(completionColor, "Quest Complete");
}
ImGui::Separator();
}
}
ImGui::EndChild();
}
}
ImGui::EndChild();
}
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(1);
ImGui::End();
}
std::shared_ptr<RogueLike::GUI::StartingSelectionWindow> mStartingSelectionWindow;
std::shared_ptr<RogueLike::GUI::HUDWindow> mHUDWindow;
std::shared_ptr<RogueLike::GUI::LevelUpWindow> mLevelUpWindow;
// Entry point for the module, run once on game boot
static void InitRogueLikeGUI() {
CVarClear(CVAR_WINDOW("RogueLikeStartingSelection"));
CVarClear(CVAR_WINDOW("RogueLikeHUD"));
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
mStartingSelectionWindow = std::make_shared<RogueLike::GUI::StartingSelectionWindow>(
CVAR_WINDOW("RogueLikeStartingSelection"), "RogueLike Starting Selection");
gui->AddGuiWindow(mStartingSelectionWindow);
mHUDWindow = std::make_shared<RogueLike::GUI::HUDWindow>(CVAR_WINDOW("RogueLikeHUD"), "RogueLike HUD");
gui->AddGuiWindow(mHUDWindow);
mLevelUpWindow =
std::make_shared<RogueLike::GUI::LevelUpWindow>(CVAR_WINDOW("RogueLikeLevelUp"), "RogueLike Level Up");
gui->AddGuiWindow(mLevelUpWindow);
SohGui::mSohMenu->AddSidebarEntry("Holiday", "RogueLike", 2);
WidgetPath path = { "Holiday", "RogueLike", SECTION_COLUMN_2 };
SohGui::mSohMenu->AddWidget(path, "RogueLikeRight", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
if (UIWidgets::Button("Reset All", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
CVarSetFloat("gRogueLike.BaseDifficulty", 5000.0f);
CVarSetFloat("gRogueLike.DifficultyGrowthRate", 1.3f);
CVarSetFloat("gRogueLike.BaseXP", 100.0f);
CVarSetFloat("gRogueLike.XPGrowthRate", 1.3f);
CVarSetInteger("gRogueLike.EnemyPlateMax", 3);
CVarSetInteger("gRogueLike.EnemyPlateChance", 25);
CVarSetInteger("gRogueLike.XPDrop.Enemies", 50);
CVarSetInteger("gRogueLike.XPDrop.Bosses", 200);
CVarSetInteger("gRogueLike.XPDrop.BusinessScrubs", 100);
CVarSetInteger("gRogueLike.XPDrop.Chests", 50);
CVarSetInteger("gRogueLike.XPDrop.Grass", 20);
CVarSetInteger("gRogueLike.XPDrop.Pots", 20);
CVarSetInteger("gRogueLike.XPDrop.Rocks", 20);
CVarSetInteger("gRogueLike.XPDrop.Trees", 20);
}
ImGui::SeparatorText("Scaling Options:");
UIWidgets::CVarSliderFloat(
"Base Difficulty", "gRogueLike.BaseDifficulty",
UIWidgets::FloatSliderOptions().Min(0.0f).Max(10000.0f).DefaultValue(5000.0f).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderFloat(
"Difficulty Growth Rate", "gRogueLike.DifficultyGrowthRate",
UIWidgets::FloatSliderOptions().Min(0.0f).Max(5.0f).DefaultValue(1.3f).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderFloat(
"Base XP Req", "gRogueLike.BaseXP",
UIWidgets::FloatSliderOptions().Min(0.0f).Max(1000.0f).DefaultValue(100.0f).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderFloat(
"XP Growth Rate", "gRogueLike.XPGrowthRate",
UIWidgets::FloatSliderOptions().Min(0.0f).Max(5.0f).DefaultValue(1.3f).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Maximum Enemies Plated", "gRogueLike.EnemyPlateMax",
UIWidgets::IntSliderOptions().Min(0).Max(10).DefaultValue(3).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Chance of Enemy Plating", "gRogueLike.EnemyPlateChance",
UIWidgets::IntSliderOptions().Min(0).Max(100).DefaultValue(25).Size(ImVec2(300.0f, 0.0f)));
ImGui::SeparatorText("XP Drop Rates:");
UIWidgets::CVarSliderInt(
"Enemies", "gRogueLike.XPDrop.Enemies",
UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(50).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Bosses", "gRogueLike.XPDrop.Bosses",
UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(200).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Business Scrubs", "gRogueLike.XPDrop.BusinessScrubs",
UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(100).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Chests", "gRogueLike.XPDrop.Chests",
UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(50).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Grass", "gRogueLike.XPDrop.Grass",
UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Pots", "gRogueLike.XPDrop.Pots",
UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Rocks", "gRogueLike.XPDrop.Rocks",
UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f)));
UIWidgets::CVarSliderInt(
"Trees", "gRogueLike.XPDrop.Trees",
UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f)));
});
path = { "Holiday", "RogueLike", SECTION_COLUMN_1 };
SohGui::mSohMenu->AddWidget(path, "RogueLikeLeft", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
ImGui::TextWrapped(
"RogueLike mode is a very unpolished proof of concept that enables you to play through the game doing "
"various things to gain XP and gaining items and progression through random rolls instead of finding them "
"at specific points in the game. There are various settings to tweak the balance on the right hand panel, "
"the current balance has not really been heavily tested so feel free to experiment and share your "
"findings. Also if there is interest some one is welcome to pick this up and polish it into a more "
"complete mode, all of it is open source.");
ImGui::SeparatorText("Cheats:");
if (!IS_ROGUELIKE) {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Must be in a RogueLike save");
return;
}
if (UIWidgets::Button("Add XP Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
RogueLike::XP::GrantXP(RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1) -
gSaveContext.ship.quest.data.rogueLike.xp + 1);
}
ImGui::SameLine();
if (UIWidgets::Button("Remove XP Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
gSaveContext.ship.quest.data.rogueLike.xp =
RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() - 1);
}
if (UIWidgets::Button("Add Difficulty Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
RogueLike::Difficulty::IncrementDifficulty(
RogueLike::Difficulty::ConvertLevelToDifficulty(RogueLike::Difficulty::GetCurrentLevel() + 1) -
gSaveContext.ship.quest.data.rogueLike.difficulty + 1);
}
ImGui::SameLine();
if (UIWidgets::Button("Remove Difficulty Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) {
gSaveContext.ship.quest.data.rogueLike.difficulty =
RogueLike::Difficulty::ConvertLevelToDifficulty(RogueLike::Difficulty::GetCurrentLevel() - 1);
}
std::string statPlusValue = "";
std::string statMinusValue = "";
if (ImGui::BeginTable("Stat Testing", 2)) {
for (auto& stat : rogueLikeStatMap) {
ImTextureID textureId =
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(stat.second.second);
statPlusValue = "+ ";
statMinusValue = "- ";
std::string statValueStr = stat.second.first;
statPlusValue += statValueStr;
statMinusValue += statValueStr;
ImGui::TableNextColumn();
if (ImGui::ImageButton(statPlusValue.c_str(), textureId, ImVec2(46.0f, 46.0f))) {
gSaveContext.ship.quest.data.rogueLike.stats[stat.first]++;
RogueLike::XP::UpdatePlayerStats();
}
ImGui::TextColored(ImVec4(0, 1, 0, 1), statPlusValue.c_str());
ImGui::TableNextColumn();
if (ImGui::ImageButton(statMinusValue.c_str(), textureId, ImVec2(46.0f, 46.0f))) {
gSaveContext.ship.quest.data.rogueLike.stats[stat.first]--;
RogueLike::XP::UpdatePlayerStats();
}
ImGui::TextColored(ImVec4(1, 0, 0, 1), statMinusValue.c_str());
}
ImGui::EndTable();
}
});
COND_HOOK(OnExitGame, true, [](int32_t fileNum) {
mStartingSelectionWindow->Hide();
mHUDWindow->Hide();
mLevelUpWindow->Hide();
});
}
static void OnLoadGame() {
mStartingSelectionWindow->Hide();
mHUDWindow->Hide();
mLevelUpWindow->Hide();
if (IS_ROGUELIKE) {
if (gSaveContext.ship.quest.data.rogueLike.lastActivity == 0) {
RogueLike::Difficulty::IndicateActivity();
mStartingSelectionWindow->Show();
}
}
COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, [] {
if (mStartingSelectionWindow->IsVisible() || mLevelUpWindow->IsVisible()) {
mHUDWindow->Hide();
gPlayState->frameAdvCtx.enabled = true;
} else {
mHUDWindow->Show();
gPlayState->frameAdvCtx.enabled = false;
}
});
}
static RegisterShipInitFunc initFunc(InitRogueLikeGUI, {});
static RegisterShipInitFunc initFunc2(OnLoadGame, { "IS_ROGUELIKE" });
+59
View File
@@ -0,0 +1,59 @@
#ifndef ROGUELIKE_GUI_H
#define ROGUELIKE_GUI_H
#include <libultraship/libultraship.h>
#include <vector>
#include <string>
#include "soh/Enhancements/RogueLike/Choices.hpp"
namespace RogueLike {
namespace GUI {
void BeginFullscreenDimmed(const char* windowName);
RogueLike::Choices::ChoiceCard* DrawChooseScreen(std::string heading,
std::vector<RogueLike::Choices::ChoiceCard>& allChoices, int rolls);
class StartingSelectionWindow final : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void Draw() override;
~StartingSelectionWindow(){};
protected:
void InitElement() override{};
void DrawElement() override{};
void UpdateElement() override{};
};
class HUDWindow final : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void Draw() override;
~HUDWindow(){};
protected:
void InitElement() override{};
void DrawElement() override{};
void UpdateElement() override{};
};
class LevelUpWindow final : public Ship::GuiWindow {
public:
using GuiWindow::GuiWindow;
void Draw() override;
~LevelUpWindow(){};
protected:
void InitElement() override{};
void DrawElement() override{};
void UpdateElement() override{};
};
extern std::shared_ptr<RogueLike::GUI::LevelUpWindow> mLevelUpWindow;
} // namespace GUI
} // namespace RogueLike
#endif
@@ -0,0 +1,74 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
extern "C" {
#include "variables.h"
#include "macros.h"
#include "functions.h"
extern PlayState* gPlayState;
}
typedef enum {
WINDOW_STATE_CHOOSE_TYPE,
WINDOW_STATE_CHOOSE_CARD,
WINDOW_STATE_APPLY,
} LevelUpWindowState;
static LevelUpWindowState state = WINDOW_STATE_CHOOSE_TYPE;
static RogueLike::Choices::ChoiceCard* typeChoice = nullptr;
static RogueLike::Choices::ChoiceCard* cardChoice = nullptr;
void RogueLike::GUI::LevelUpWindow::Draw() {
if (!IsVisible()) {
return;
}
BeginFullscreenDimmed("RogueLike Level Up");
switch (state) {
case WINDOW_STATE_CHOOSE_TYPE: {
typeChoice = DrawChooseScreen("Choose.", RogueLike::Choices::All, 10);
if (typeChoice != nullptr) {
state = WINDOW_STATE_CHOOSE_CARD;
}
} break;
case WINDOW_STATE_CHOOSE_CARD: {
switch (typeChoice->value) {
case 0: // Stat
cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Stats, 10);
break;
case 1: // Item
cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Items, 10);
break;
case 2: // Equipment
cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Equipment, 10);
break;
case 3: // Song
cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Songs, 10);
break;
default:
break;
}
if (cardChoice != nullptr) {
state = WINDOW_STATE_APPLY;
}
} break;
default: {
// Apply choices
cardChoice->onSelect(cardChoice->value);
RogueLike::XP::UpdatePlayerStats();
// Close window and continue game
this->Hide();
gPlayState->frameAdvCtx.enabled = false;
// Reset state
state = WINDOW_STATE_CHOOSE_TYPE;
typeChoice = nullptr;
cardChoice = nullptr;
} break;
}
ImGui::End();
}
@@ -0,0 +1,71 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
extern "C" {
#include "variables.h"
#include "macros.h"
#include "functions.h"
extern PlayState* gPlayState;
}
typedef enum {
WINDOW_STATE_LOCATION,
WINDOW_STATE_ITEM,
WINDOW_STATE_SONG,
WINDOW_STATE_APPLY,
} StartingSelectionWindowState;
static StartingSelectionWindowState state = WINDOW_STATE_LOCATION;
static RogueLike::Choices::ChoiceCard* locationChoice = nullptr;
static RogueLike::Choices::ChoiceCard* itemChoice = nullptr;
static RogueLike::Choices::ChoiceCard* songChoice = nullptr;
void RogueLike::GUI::StartingSelectionWindow::Draw() {
if (!IsVisible()) {
return;
}
BeginFullscreenDimmed("RogueLike Starting Selection");
switch (state) {
case WINDOW_STATE_LOCATION: {
locationChoice = DrawChooseScreen("Where will you begin your journey?", RogueLike::Choices::Locations, 20);
if (locationChoice != nullptr) {
state = WINDOW_STATE_ITEM;
}
} break;
case WINDOW_STATE_ITEM: {
itemChoice = DrawChooseScreen("What will you take?", RogueLike::Choices::Items, 20);
if (itemChoice != nullptr) {
state = WINDOW_STATE_SONG;
}
} break;
case WINDOW_STATE_SONG: {
songChoice = DrawChooseScreen("What is your tune of choice?", RogueLike::Choices::Songs, 20);
if (songChoice != nullptr) {
state = WINDOW_STATE_APPLY;
}
} break;
default: {
// Apply choices
locationChoice->onSelect(locationChoice->value);
itemChoice->onSelect(itemChoice->value);
songChoice->onSelect(songChoice->value);
// Close window and reload game state
this->Hide();
SET_NEXT_GAMESTATE(&gPlayState->state, Play_Init, PlayState);
gPlayState->state.running = false;
// Reset state
state = WINDOW_STATE_LOCATION;
locationChoice = nullptr;
itemChoice = nullptr;
songChoice = nullptr;
} break;
}
ImGui::End();
}
@@ -0,0 +1,21 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "functions.h"
}
static void OnLoadGame(int32_t fileNum) {
ShipInit::Init("IS_ROGUELIKE");
if (IS_ROGUELIKE) {
Flags_SetEventChkInf(EVENTCHKINF_OPENED_THE_DOOR_OF_TIME);
Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD);
Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_MIDO_AFTER_DEKU_TREES_DEATH);
}
}
static RegisterShipInitFunc
initFunc([]() { GameInteractor::Instance->RegisterGameHook<GameInteractor::OnLoadGame>(OnLoadGame); }, {});
+681
View File
@@ -0,0 +1,681 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include "soh/ActorDB.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
#include "soh/Notification/Notification.h"
extern "C" {
#include "variables.h"
#include <macros.h>
#include <functions.h>
#include "overlays/actors/ovl_Bg_Mjin/z_bg_mjin.h"
#include "overlays/actors/ovl_En_Vm/z_en_vm.h"
extern PlayState* gPlayState;
s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId);
}
// clang-format off
std::vector<RogueLikeQuestObject> rogueLikeQuestList = {
{ RL_QUEST_HF_TRIAL_A, "Ganon's Fury I", "Watch out!", RL_QUEST_ACTIVE, 0, 1 },
{ RL_QUEST_HF_TRIAL_B, "Ganon's Fury II", "Get to Gerudo Valley\nbefore time runs out!", RL_QUEST_ACTIVE, 0, 1 },
{ RL_QUEST_KF_HOPOFFAITH, "Hop of Faith", "Sidehop from the fence\nabove the waterfall and land\non the middle platform.", RL_QUEST_ACTIVE, 0, 1},
{ RL_QUEST_KF_STRONGMAN, "Toe Crushers", "Mido likes rock, show them\nthat we don't!", RL_QUEST_ACTIVE, 0, 11 },
{ RL_QUEST_KV_POTHUNT, "The Pot Thickens", "A magical pot with extra lives?\nFind out how many!", RL_QUEST_ACTIVE, 0, 4 },
{ RL_QUEST_KV_STALFOS, "Stal-Not-So-Child", "The Stalchild in Hyrule Field have\ngotten bigger, take them out!", RL_QUEST_ACTIVE, 0, 5 },
{ RL_QUEST_ZD_POTTERY, "A Smashing View", "Toss a pot off the edge\nof the waterfall.", RL_QUEST_ACTIVE, 0, 1 },
};
std::vector<Vec3f> potHuntLocations = {
{ 150.378f, 300.0f, 1166.648f },
{ 6.151f, 755.0f, -91.802f },
{ 1760.740f, 542.62f, 534.236f },
{ -381.964f, 240.0f, 1597.884f },
};
const std::vector<std::pair<Vec3f, int16_t>> trialAActorSpawnList = {
{ { 563.838f, -0, 3059.409f }, -27275 },
{ { 678.031f, -0, 2563.164f }, -10891 },
{ { 185.132f, -0, 2404.207f }, -27275 },
{ { 36.108f, -0, 2925.440f }, -10891 },
{ { 335.571f, 20.0f, 2677.854f }, 0 },
};
// clang-format on
Actor* trialActorSlot = NULL;
extern std::vector<RogueLikeQuestObject> activeQuests;
static std::vector<Vec3f> potHuntAvailability;
static std::vector<Actor*> currentTrialActorList;
static bool potHuntActorSpawned = false;
static bool sendConditionMessage = true;
bool CheckActiveQuestById(u8 questId) {
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
return true;
}
}
return false;
}
bool CheckQuestGoalCompleteById(u8 questId) {
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
return (quest.questProgress == quest.questGoal);
}
}
return false;
}
bool CheckQuestCompletedById(u8 questId) {
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
return (quest.questStatus);
}
}
return false;
}
void SendQuestConditionMessage(u8 questId) {
if (!sendConditionMessage) {
return;
}
std::string message = "";
ImVec4 color = ImVec4(1, 1, 1, 1);
switch (questId) {
case RL_QUEST_KV_POTHUNT:
message = "A Magical Pot has appeared nearby.";
color = ImVec4(0, 0.25f, 0.75f, 1);
break;
case RL_QUEST_HF_TRIAL_A:
message = "Come back when you have a sword...";
color = ImVec4(1, 0, 0, 1);
break;
case RL_QUEST_HF_TRIAL_B:
message = "Come back when you're faster...";
color = ImVec4(1, 0, 0, 1);
break;
default:
return;
}
Notification::Emit({
.message = message,
.messageColor = color,
});
sendConditionMessage = false;
}
void RogueLike::Quests::CompleteQuestById(u8 questId) {
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
quest.questStatus = RL_QUEST_COMPLETE;
break;
}
}
}
void RogueLike::Quests::RemoveQuestById(u8 questId) {
int index = -1;
for (int i = 0; i < activeQuests.size(); i++) {
if (activeQuests[i].questId == questId) {
index = i;
break;
}
}
if (index != -1) {
activeQuests.erase(activeQuests.begin() + index);
}
}
void RogueLike::Quests::AddQuestById(u8 questId) {
if (CheckActiveQuestById(questId)) {
return;
}
activeQuests.push_back(rogueLikeQuestList[questId]);
}
u16 GetQuestProgress(u8 questId) {
if (!CheckActiveQuestById(questId)) {
return 0;
}
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
return quest.questProgress;
}
}
return 0;
}
u16 GetQuestGoal(u8 questId) {
if (!CheckActiveQuestById(questId)) {
return 0;
}
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
return quest.questGoal;
}
}
return 0;
}
u16 DetermineInitialQuestProgress(u8 questId, int16_t progressActor) {
ActorListEntry actorList = gPlayState->actorCtx.actorLists[ACTORCAT_PROP];
u16 initialProgress = GetQuestGoal(questId);
if (questId == RL_QUEST_KF_STRONGMAN) {
initialProgress++;
}
Actor* currentActor = actorList.head;
while (currentActor != nullptr) {
if (currentActor->id == progressActor) {
initialProgress--;
}
currentActor = currentActor->next;
}
return initialProgress;
}
void RogueLike::Quests::UpdateQuestProgress(u8 questId) {
if (!CheckActiveQuestById(questId)) {
return;
}
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
quest.questProgress++;
break;
}
}
}
void RogueLike::Quests::SetQuestProgress(u8 questId, u16 progress) {
if (!CheckActiveQuestById(questId)) {
return;
}
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
quest.questProgress = progress;
break;
}
}
}
void RogueLike::Quests::ResetQuestProgress(u8 questId) {
if (!CheckActiveQuestById(questId)) {
return;
}
if (CheckQuestGoalCompleteById(questId)) {
return;
}
for (auto& quest : activeQuests) {
if (quest.questId == questId) {
if (questId == RL_QUEST_KV_POTHUNT) {
potHuntAvailability.clear();
}
quest.questProgress = 0;
break;
}
}
}
// StartQuest() - Used for Quest Specific Functions.
void StartQuest(u8 questId) {
uint32_t index = 0;
switch (questId) {
case RL_QUEST_HF_TRIAL_A:
if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_A)) {
GameInteractor::RawAction::SetWeatherStorm(true);
for (auto& slot : trialAActorSpawnList) {
if (slot.first.y != 20.0f) {
trialActorSlot =
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_HIDAN_FWBIG, slot.first.x,
slot.first.y, slot.first.z, 0, slot.second, 0, 0, false);
currentTrialActorList.push_back(trialActorSlot);
} else {
EnVm* beamosActor =
(EnVm*)Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_VM, slot.first.x,
slot.first.y, slot.first.z, 0, slot.second, 0, 0, false);
beamosActor->beamSightRange = 400.0f;
currentTrialActorList.push_back(&beamosActor->actor);
}
index++;
}
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ZF, 255.359f, -0, 3134.437f, 0, 0, 0, -2,
false);
} else {
RogueLike::Quests::CompleteQuestById(RL_QUEST_HF_TRIAL_A);
uint32_t reward = RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1);
RogueLike::XP::GrantXP(reward);
for (auto& kill : currentTrialActorList) {
Actor_Kill(kill);
kill = NULL;
}
currentTrialActorList.clear();
GameInteractor::RawAction::SetWeatherStorm(false);
}
break;
case RL_QUEST_HF_TRIAL_B:
if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_B)) {
GameInteractor::RawAction::SetWeatherStorm(true);
gSaveContext.timerState = 6;
gSaveContext.timerSeconds = 70;
} else {
RogueLike::Quests::CompleteQuestById(RL_QUEST_HF_TRIAL_B);
uint32_t reward = RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1);
gSaveContext.timerState = 0;
gSaveContext.timerSeconds = 0;
GameInteractor::RawAction::SetWeatherStorm(false);
}
break;
case RL_QUEST_KV_POTHUNT:
if (!CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT)) {
if (potHuntAvailability.size() == 0) {
potHuntAvailability = potHuntLocations;
}
u8 potRoll = rand() % potHuntAvailability.size();
Vec3f spawnPoint = potHuntAvailability[potRoll];
Actor* potActor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_OBJ_TSUBO, spawnPoint.x,
spawnPoint.y, spawnPoint.z, 0, 0, 0, 256, false);
Actor_SetColorFilter(potActor, 0x1000, 150, 0, 1000);
potHuntAvailability.erase(potHuntAvailability.begin() + potRoll);
sendConditionMessage = true;
SendQuestConditionMessage(RL_QUEST_KV_POTHUNT);
}
break;
case RL_QUEST_KV_STALFOS:
if (!CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS)) {
float centerX = 2475.3f;
float centerZ = 496.3f;
float radius = 100.0f;
for (int i = 0; i < GetQuestGoal(RL_QUEST_KV_STALFOS); i++) {
float angle = i * (2 * M_PI / 5);
float spawnX = centerX + radius * cosf(angle);
float spawnZ = centerZ + radius * sinf(angle);
if (ActorDB::Instance->RetrieveEntry(ACTOR_EN_TEST).entry.valid) {
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_TEST, spawnX, -4.7f, spawnZ, 0, 0, 0, 1,
0);
}
}
}
break;
default:
break;
}
}
RogueLikeQuest FindTrialByLocation(Actor* trialActor) {
switch (gPlayState->sceneNum) {
case SCENE_HYRULE_FIELD:
if (trialActor->world.pos.x == 335.571f && trialActor->world.pos.z == 2677.854f) {
if (LINK_IS_ADULT && ((EQUIP_FLAG_SWORD_MASTER & gSaveContext.inventory.equipment) ||
(EQUIP_FLAG_SWORD_BGS & gSaveContext.inventory.equipment))) {
return RL_QUEST_HF_TRIAL_A;
} else {
SendQuestConditionMessage(RL_QUEST_HF_TRIAL_A);
}
}
if (trialActor->world.pos.x == 1490.550f && trialActor->world.pos.z == 8760.643f) {
if (LINK_IS_ADULT) {
return RL_QUEST_HF_TRIAL_B;
}
}
break;
default:
break;
}
return RL_QUEST_ID_MAX;
}
static void InitRogueLikeQuests() {
activeQuests.clear();
currentTrialActorList.clear();
}
static void OnLoadGame() {
activeQuests.clear();
for (auto& load : gSaveContext.ship.quest.data.rogueLike.quests) {
if (load.questDescription != NULL) {
activeQuests.push_back(load);
}
}
COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, []() {
Player* player = GET_PLAYER(gPlayState);
RogueLikeQuest questId = RL_QUEST_ID_MAX;
static bool hopOfFaithStart = false;
static bool trialTimerInit = false;
static uint32_t trialTimer = 0;
if (trialTimerInit == false) {
trialTimer = gPlayState->gameplayFrames;
trialTimerInit = true;
}
if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_B) && !CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_B)) {
if (gSaveContext.timerState == 6) {
if (gSaveContext.timerSeconds > 0) {
if (trialTimer <= gPlayState->gameplayFrames - 20) {
gSaveContext.timerSeconds--;
trialTimer = gPlayState->gameplayFrames;
}
} else if (gSaveContext.timerSeconds <= 0) {
gSaveContext.health = 4;
gSaveContext.timerState = 0;
gSaveContext.timerSeconds = 0;
RogueLike::Quests::RemoveQuestById(RL_QUEST_HF_TRIAL_B);
sendConditionMessage = true;
SendQuestConditionMessage(RL_QUEST_HF_TRIAL_B);
}
}
}
if (CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH) && !CheckQuestGoalCompleteById(RL_QUEST_KF_HOPOFFAITH)) {
bool isHopping = (player->stateFlags2 & PLAYER_STATE2_HOPPING);
if (!hopOfFaithStart && isHopping && player->actor.world.pos.y >= 360.0f) {
hopOfFaithStart = true;
}
if (hopOfFaithStart && !isHopping) {
hopOfFaithStart = false;
if ((player->actor.world.pos.x >= 318.0f && player->actor.world.pos.x <= 418.0f) &&
(player->actor.world.pos.z >= -227.6f && player->actor.world.pos.z <= -126.5f)) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KF_HOPOFFAITH);
}
}
}
if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) {
Actor* trialActor =
Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_BG_MJIN, ACTORCAT_BG, 45.0f);
if (trialActor != NULL) {
questId = FindTrialByLocation(trialActor);
if (questId != RL_QUEST_ID_MAX) {
if (!CheckActiveQuestById(questId)) {
RogueLike::Quests::AddQuestById(questId);
StartQuest(questId);
}
}
}
}
});
COND_HOOK(OnSceneInit, IS_ROGUELIKE, [](u16 sceneNum) {
if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_B) && gSaveContext.timerState == 6 &&
gPlayState->sceneNum == SCENE_GERUDO_VALLEY) {
if (gSaveContext.timerSeconds > 0) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_HF_TRIAL_B);
}
}
for (auto& quest : activeQuests) {
RogueLike::Quests::ResetQuestProgress(quest.questId);
}
sendConditionMessage = true;
});
COND_HOOK(OnRoomInit, IS_ROGUELIKE, [](u16 roomNum) {
if (gPlayState->sceneNum == SCENE_KOKIRI_FOREST) {
if (CheckActiveQuestById(RL_QUEST_KF_STRONGMAN)) {
if (!CheckQuestCompletedById(RL_QUEST_KF_STRONGMAN)) {
RogueLike::Quests::ResetQuestProgress(RL_QUEST_KF_STRONGMAN);
}
}
}
});
COND_HOOK(OnSceneSpawnActors, IS_ROGUELIKE, []() {
if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) {
if (CheckActiveQuestById(RL_QUEST_KV_STALFOS)) {
StartQuest(RL_QUEST_KV_STALFOS);
}
Object_Spawn(&gPlayState->objectCtx, OBJECT_MJIN);
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_MJIN, 335.571f, -0.0f, 2677.854f, 0, 0, 0, 1,
false);
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_MJIN, 1490.550f, -135.0f, 8760.643f, 0, 0, 0, 1,
false);
}
});
COND_HOOK(OnActorKill, IS_ROGUELIKE, [](void* actor) {
Actor* refActor = (Actor*)actor;
switch (refActor->id) {
case ACTOR_EN_TEST:
if (CheckActiveQuestById(RL_QUEST_KV_STALFOS) && gPlayState->sceneNum == SCENE_HYRULE_FIELD) {
if (!CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS)) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KV_STALFOS);
}
}
break;
case ACTOR_OBJ_TSUBO:
if (gPlayState->sceneNum == SCENE_ZORAS_DOMAIN) {
if (CheckActiveQuestById(RL_QUEST_ZD_POTTERY)) {
if (!(CheckQuestGoalCompleteById(RL_QUEST_ZD_POTTERY) &&
CheckQuestCompletedById(RL_QUEST_ZD_POTTERY))) {
if (GET_PLAYER(gPlayState)->actor.world.pos.y >= 830.0f &&
refActor->world.pos.y <= 800.0f) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_ZD_POTTERY);
}
}
}
} else if (gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE) {
if (CheckActiveQuestById(RL_QUEST_KV_POTHUNT) && refActor->params == 256) {
if (!(CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT) &&
CheckQuestCompletedById(RL_QUEST_KV_POTHUNT))) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KV_POTHUNT);
StartQuest(RL_QUEST_KV_POTHUNT);
}
}
}
break;
case ACTOR_EN_ISHI:
if (refActor->world.pos.x == refActor->home.pos.x && refActor->world.pos.z == refActor->home.pos.z) {
return;
}
if (CheckActiveQuestById(RL_QUEST_KF_STRONGMAN) && gPlayState->sceneNum == SCENE_KOKIRI_FOREST &&
gPlayState->roomCtx.curRoom.num == 0) {
if (!CheckQuestGoalCompleteById(RL_QUEST_KF_STRONGMAN)) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KF_STRONGMAN);
}
}
break;
case ACTOR_EN_ZF:
if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) {
if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_A)) {
if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_A)) {
RogueLike::Quests::UpdateQuestProgress(RL_QUEST_HF_TRIAL_A);
StartQuest(RL_QUEST_HF_TRIAL_A);
}
}
}
break;
default:
break;
}
});
// RL_QUEST_KV_STALFOS
COND_ID_HOOK(OnOpenText, 0x503e, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 4);
auto messageEntry = CustomMessage("Stalfos have been attacking our Cucco's, please help us!" + endOfMessage);
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
COND_ID_HOOK(OnOpenText, 0x5042, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
if (CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS) && !CheckQuestCompletedById(RL_QUEST_KV_STALFOS)) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("Thank you for saving our cucco's!" + endOfMessage);
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 20);
RogueLike::Quests::CompleteQuestById(RL_QUEST_KV_STALFOS);
*loadFromMessageTable = false;
}
});
// RL_QUEST_ZD_POTTERY
COND_ID_HOOK(OnOpenText, 0x4006, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("Have you ever heard the sound of ceramic shattering against the water? You "
"should give it a try, it's beautiful!" +
endOfMessage);
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
RogueLike::Quests::AddQuestById(RL_QUEST_ZD_POTTERY);
*loadFromMessageTable = false;
});
COND_ID_HOOK(OnOpenText, 0x4007, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("" + endOfMessage);
if (!CheckQuestGoalCompleteById(RL_QUEST_ZD_POTTERY)) {
messageEntry = CustomMessage(
"Need a tip? Take one of the Pots outside of the Shop and toss it over the top of the waterfall." +
endOfMessage);
} else {
messageEntry = CustomMessage(
"The sound, wasn't that exhilarating? There may be a few more pots if you fancy another go." +
endOfMessage);
RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10);
RogueLike::Quests::CompleteQuestById(RL_QUEST_ZD_POTTERY);
}
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
// RL_QUEST_KF_STRONGMAN
COND_ID_HOOK(OnOpenText, 0x1004, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("" + endOfMessage);
if (!CheckQuestGoalCompleteById(RL_QUEST_KF_STRONGMAN)) {
messageEntry = CustomMessage(
"Stupid Mido likes these stupid rocks! Don't just stand there, help me smash them!" + endOfMessage);
if (!CheckActiveQuestById(RL_QUEST_KF_STRONGMAN)) {
RogueLike::Quests::AddQuestById(RL_QUEST_KF_STRONGMAN);
RogueLike::Quests::SetQuestProgress(
RL_QUEST_KF_STRONGMAN, DetermineInitialQuestProgress(RL_QUEST_KF_STRONGMAN, ACTOR_EN_ISHI));
}
} else {
messageEntry = CustomMessage("Thanks for being one of the good guys! Oh, this one? Don't worry, I'll have "
"it smashed by your 17th birthday." +
endOfMessage);
RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10);
RogueLike::Quests::CompleteQuestById(RL_QUEST_KF_STRONGMAN);
}
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
// RL_QUEST_KF_HOPOFFAITH
COND_ID_HOOK(OnOpenText, 0x10d7, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 4);
auto messageEntry = CustomMessage("" + endOfMessage);
if (!CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH)) {
messageEntry =
CustomMessage("Wow, you came all the way to see me? Not afraid of heights I see." + endOfMessage);
} else {
Message_ContinueTextbox(gPlayState, 0x10d8);
}
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
COND_ID_HOOK(OnOpenText, 0x10d8, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("" + endOfMessage);
if (!CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH)) {
messageEntry =
CustomMessage("If we're going to continue meeting like this, let's see what you got!" + endOfMessage);
RogueLike::Quests::AddQuestById(RL_QUEST_KF_HOPOFFAITH);
} else {
if (!CheckQuestGoalCompleteById(RL_QUEST_KF_HOPOFFAITH)) {
messageEntry = CustomMessage("Can't read the Quest List, huh. See that fence above the waterfall? "
"Sidehop off of it and land on the middle platform below." +
endOfMessage);
} else {
messageEntry =
CustomMessage("Hey, nice distance! They say you can unload doors doing that." + endOfMessage);
if (!CheckQuestCompletedById(RL_QUEST_KF_HOPOFFAITH)) {
RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10);
RogueLike::Quests::CompleteQuestById(RL_QUEST_KF_HOPOFFAITH);
}
}
}
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
// RL_QUEST_KV_POTHUNT
COND_ID_HOOK(OnOpenText, 0x5036, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) {
auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2);
auto messageEntry = CustomMessage("" + endOfMessage);
if (!CheckActiveQuestById(RL_QUEST_KV_POTHUNT)) {
messageEntry = CustomMessage("I've noticed you have a knack for smashing things. I developed a special Pot "
"that could use testing, it's hidden around here somewhere." +
endOfMessage);
RogueLike::Quests::AddQuestById(RL_QUEST_KV_POTHUNT);
StartQuest(RL_QUEST_KV_POTHUNT);
} else {
if (!CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT)) {
messageEntry = CustomMessage("It's hidden around here somewhere, get hunting!" + endOfMessage);
} else {
if (!CheckQuestCompletedById(RL_QUEST_KV_POTHUNT)) {
messageEntry =
CustomMessage("Nicely done! It's not much but here's something for your help." + endOfMessage);
RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10);
RogueLike::Quests::CompleteQuestById(RL_QUEST_KV_POTHUNT);
} else {
messageEntry = CustomMessage("Thanks for your help." + endOfMessage);
}
}
}
messageEntry.AutoFormat();
messageEntry.LoadIntoFont();
*loadFromMessageTable = false;
});
}
static RegisterShipInitFunc initFunc(InitRogueLikeQuests, {});
static RegisterShipInitFunc initFunc2(OnLoadGame, { "IS_ROGUELIKE" });
+23
View File
@@ -0,0 +1,23 @@
#ifndef ROGUELIKE_QUESTS_H
#define ROGUELIKE_QUESTS_H
extern "C" {
#include <z64math.h>
}
namespace RogueLike {
namespace Quests {
void CompleteQuestById(u8 questId);
void AddQuestById(u8 questId);
void RemoveQuestById(u8 questId);
void UpdateQuestProgress(u8 questId);
void SetQuestProgress(u8 questId, u16 progress);
void ResetQuestProgress(u8 questId);
} // namespace Quests
} // namespace RogueLike
#endif
@@ -0,0 +1,9 @@
#include "RogueLike.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
// Entry point for the module, run once on game boot
static void InitRogueLike() {
}
static RegisterShipInitFunc initFunc(InitRogueLike, {});
@@ -0,0 +1,14 @@
#ifndef ROGUELIKE_H
#define ROGUELIKE_H
#include "GUI/GUI.h"
#include "XP.h"
#include "Difficulty.h"
#include "Quests.h"
#include "Types.h"
extern std::vector<RogueLikeQuestObject> activeQuests;
namespace RogueLike {} // namespace RogueLike
#endif // ROGUELIKE_H
+45
View File
@@ -0,0 +1,45 @@
#ifndef ROGUELIKE_TYPES_H
#define ROGUELIKE_TYPES_H
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
RL_HEALTH,
RL_ATTACK,
RL_DEFENSE,
RL_SPEED,
RL_MAX,
} RoguelikeStats;
typedef enum {
RL_QUEST_ACTIVE,
RL_QUEST_COMPLETE,
} RogueLikeQuestStatus;
typedef enum {
RL_QUEST_HF_TRIAL_A,
RL_QUEST_HF_TRIAL_B,
RL_QUEST_KF_HOPOFFAITH,
RL_QUEST_KF_STRONGMAN,
RL_QUEST_KV_POTHUNT,
RL_QUEST_KV_STALFOS,
RL_QUEST_ZD_POTTERY,
RL_QUEST_ID_MAX,
} RogueLikeQuest;
typedef struct {
u8 questId;
const char* questName;
const char* questDescription;
u8 questStatus;
u16 questProgress;
u16 questGoal;
} RogueLikeQuestObject;
#ifdef __cplusplus
}
#endif
#endif // ROGUELIKE_TYPES_H
+124
View File
@@ -0,0 +1,124 @@
#include "soh/Enhancements/RogueLike/RogueLike.h"
#include "soh/Enhancements/custom-item/CustomItem.h"
extern "C" {
#include "variables.h"
#include "functions.h"
#include "macros.h"
extern PlayState* gPlayState;
}
extern std::shared_ptr<RogueLike::GUI::LevelUpWindow> mLevelUpWindow;
extern std::map<RoguelikeStats, std::pair<std::string, std::string>> rogueLikeStatMap;
#define BASE_XP CVarGetFloat("gRogueLike.BaseXP", 100.0f)
#define GROWTH_RATE CVarGetFloat("gRogueLike.XPGrowthRate", 1.3f)
float RogueLike::XP::GetProgressToNextLevel() {
u32 currentXP = gSaveContext.ship.quest.data.rogueLike.xp;
u32 currentLevel = GetCurrentLevel();
u32 xpForCurrentLevel = ConvertLevelToXP(currentLevel);
u32 xpForNextLevel = ConvertLevelToXP(currentLevel + 1);
return static_cast<float>(currentXP - xpForCurrentLevel) / static_cast<float>(xpForNextLevel - xpForCurrentLevel);
}
u32 RogueLike::XP::GetCurrentLevel() {
return RogueLike::XP::ConvertXPToLevel(gSaveContext.ship.quest.data.rogueLike.xp);
}
u32 RogueLike::XP::ConvertXPToLevel(u32 xp) {
return static_cast<u32>(logf((xp * (GROWTH_RATE - 1) / BASE_XP) + 1) / logf(GROWTH_RATE));
}
u32 RogueLike::XP::ConvertLevelToXP(u32 level) {
return static_cast<u32>(BASE_XP * ((powf(GROWTH_RATE, level) - 1) / (GROWTH_RATE - 1)));
}
void RogueLike::XP::GrantXP(u32 amount) {
u32 oldLevel = GetCurrentLevel();
gSaveContext.ship.quest.data.rogueLike.xp += amount;
RogueLike::Difficulty::IndicateActivity();
u32 newLevel = GetCurrentLevel();
if (newLevel != oldLevel) {
Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME);
mLevelUpWindow->Show();
}
}
void RogueLike::XP::SpawnXPOrb(Vec3f spawnPos, u32 amount, int16_t flags) {
CustomItem::Spawn(
spawnPos.x, spawnPos.y + 10.0f, spawnPos.z, 0, flags, amount,
[](Actor* actor, PlayState* play) {
RogueLike::XP::GrantXP(CUSTOM_ITEM_PARAM);
Sfx_PlaySfxCentered(NA_SE_SY_RUPY_COUNT);
},
[](Actor* actor, PlayState* play) {
Matrix_Scale(15.0f, 15.0f, 15.0f, MTXMODE_APPLY);
Matrix_Translate(0.0f, -40.0f, 0.0f, MTXMODE_APPLY);
u32 amount = CUSTOM_ITEM_PARAM;
GetItemDrawID drawId = GID_RUPEE_GREEN;
if (amount >= 200) {
drawId = GID_RUPEE_GOLD;
} else if (amount >= 50) {
drawId = GID_RUPEE_PURPLE;
} else if (amount >= 20) {
drawId = GID_RUPEE_RED;
} else if (amount >= 5) {
drawId = GID_RUPEE_BLUE;
}
GetItem_Draw(play, drawId);
// Slowly move towards the player
Player* player = GET_PLAYER(play);
// Don't magnet till it hits the ground
if (actor->bgCheckFlags & 1 && !Player_InBlockingCsMode(gPlayState, player)) {
if (actor->xzDistToPlayer < 100.0f) {
s16 targetYaw = Actor_WorldYawTowardActor(actor, &player->actor);
actor->world.rot.y = targetYaw;
// the further away, the slower it moves
const f32 desiredSpeed = ((actor->xzDistToPlayer - 10.0f) / 90.0f) * (0.01f - 3.0f) + 3.0f;
actor->speedXZ = desiredSpeed;
}
if (actor->xzDistToPlayer < 10.0f) {
CUSTOM_ITEM_FLAGS |= CustomItem::KILL_ON_TOUCH;
}
}
});
}
void RogueLike::XP::SpawnXPGroup(Vec3f spawnPos, u32 amount) {
u32 remainingAmount = amount;
while (remainingAmount > 0) {
std::vector<u32> orbSizes = { 1 };
if (remainingAmount >= 200)
orbSizes.push_back(200);
if (remainingAmount >= 50)
orbSizes.push_back(50);
if (remainingAmount >= 20)
orbSizes.push_back(20);
if (remainingAmount >= 5)
orbSizes.push_back(5);
u32 orbAmount = orbSizes[Rand_ZeroOne() * orbSizes.size()];
SpawnXPOrb(spawnPos, orbAmount);
remainingAmount -= orbAmount;
}
}
void RogueLike::XP::UpdatePlayerStats() {
gSaveContext.healthCapacity = 0x30 + (gSaveContext.ship.quest.data.rogueLike.stats[RL_HEALTH] * 0x10);
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef ROGUELIKE_XP_H
#define ROGUELIKE_XP_H
#include "soh/Enhancements/custom-item/CustomItem.h"
extern "C" {
#include <z64math.h>
}
namespace RogueLike {
namespace XP {
float GetProgressToNextLevel();
u32 GetCurrentLevel();
u32 ConvertXPToLevel(u32 xp);
u32 ConvertLevelToXP(u32 level);
void GrantXP(u32 amount);
void SpawnXPOrb(Vec3f spawnPos, u32 amount, int16_t flags = CustomItem::STOP_BOBBING | CustomItem::TOSS_ON_SPAWN);
void SpawnXPGroup(Vec3f spawnPos, u32 amount);
void UpdatePlayerStats();
} // namespace XP
} // namespace RogueLike
#endif
@@ -615,23 +615,6 @@ void CosmeticsUpdateTick() {
5. GFX Command: The GFX command you want to insert
*/
void ApplyOrResetCustomGfxPatches(bool manualChange) {
if (manualChange) {
PATCH_GFX(object_wood02_DL_007968, "Tree1", "gLetItSnow", 17, gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000090, "Tree2", "gLetItSnow", 17, gsDPSetPrimColor(0, 0, 200, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000340, "Tree3", "gLetItSnow", 17, gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(object_wood02_DL_000340, "Tree4", "gLetItSnow", 24, gsDPSetPrimColor(0, 0, 255, 255, 255, 255));
PATCH_GFX(spot00_room_0DL_0139A8, "Path1", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot00_room_0DL_013250, "Path2", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot00_room_0DL_0143C8, "Path3", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot04_room_0DL_018048, "Path4", "gLetItSnow", 24, gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot04_room_1DL_007810, "Path5", "gLetItSnow", 24, gsDPSetPrimColor(0, 0, 100, 150, 255, 60));
PATCH_GFX(spot20_room_0DL_0062D0, "Path6", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_004460, "Path8", "gLetItSnow", 31, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_004460, "Path9", "gLetItSnow", 118, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot20_room_0DL_0065E8, "Path10", "gLetItSnow", 24, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot03_room_0DL_00C4B0, "Path11", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
PATCH_GFX(spot15_room_0DL_00C748, "Path12", "gLetItSnow", 23, gsDPSetPrimColor(0, 0, 200, 230, 255, 30));
}
static CosmeticOption& magicFaroresPrimary = cosmeticOptions.at("Magic.FaroresPrimary");
if (manualChange || CVarGetInteger(magicFaroresPrimary.rainbowCvar, 0)) {
Color_RGBA8 color = CVarGetColor(magicFaroresPrimary.valuesCvar, magicFaroresPrimary.defaultColor);
@@ -1940,14 +1923,6 @@ void DrawSillyTab() {
UIWidgets::Separator(true, true, 2.0f, 2.0f);
UIWidgets::CVarCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"),
UIWidgets::CheckboxOptions()
.Color(THEME_COLOR)
.Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for "
"December holidays.\nWill reset on restart outside of December 23-25."));
UIWidgets::Separator(true, true, 2.0f, 2.0f);
if (UIWidgets::CVarSliderFloat("Link Body Size", CVAR_COSMETIC("Link.BodySize.Value"),
UIWidgets::FloatSliderOptions()
.Format("%.3f")
@@ -1,27 +1,48 @@
#include "CustomCollectible.h"
#include <libultraship/libultraship.h>
#include "CustomItem.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/custom-message/CustomMessageManager.h"
extern "C" {
#include "z64actor.h"
#include "functions.h"
#include "variables.h"
#include "macros.h"
#include "objects/object_md/object_md.h"
extern PlayState* gPlayState;
}
EnItem00* CustomCollectible::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc,
ActorFunc drawFunc) {
// #region These were copied from z_en_item00.c
static ColliderCylinderInit sCylinderInit = {
{
COLTYPE_NONE,
AT_NONE,
AC_ON | AT_TYPE_PLAYER,
OC1_NONE,
OC2_NONE,
COLSHAPE_CYLINDER,
},
{
ELEMTYPE_UNK0,
{ 0x00000000, 0x00, 0x00 },
{ 0x00000010, 0x00, 0x00 },
TOUCH_NONE | TOUCH_SFX_NORMAL,
BUMP_ON,
OCELEM_NONE,
},
{ 10, 30, 0, { 0, 0, 0 } },
};
static InitChainEntry sInitChain[] = {
ICHAIN_F32(targetArrowOffset, 2000, ICHAIN_STOP),
};
// #endregion
EnItem00* CustomItem::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc,
ActorFunc drawFunc) {
if (!gPlayState) {
return nullptr;
}
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ITEM00, posX, posY, posZ, flags, rot, params,
ITEM00_NONE, 0);
ITEM00_NONE, false);
EnItem00* enItem00 = (EnItem00*)actor;
if (actionFunc != NULL) {
@@ -35,88 +56,104 @@ EnItem00* CustomCollectible::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 fl
return enItem00;
}
void CustomCollectible_Init(Actor* actor, PlayState* play) {
void CustomItem_Init(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) {
if (CUSTOM_ITEM_FLAGS & CustomItem::STOP_BOBBING) {
actor->shape.yOffset = 1250.0f;
} else {
actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) {
if (CUSTOM_ITEM_FLAGS & CustomItem::HIDE_TILL_OVERHEAD) {
Actor_SetScale(actor, 0.0f);
} else {
Actor_SetScale(actor, 0.015f);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) {
if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) {
Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::TOSS_ON_SPAWN) {
if (CUSTOM_ITEM_FLAGS & CustomItem::TOSS_ON_SPAWN) {
actor->velocity.y = 8.0f;
actor->speedXZ = 2.0f;
actor->gravity = -1.4f;
actor->world.rot.y = Rand_ZeroOne() * 40000.0f;
}
if (CUSTOM_ITEM_FLAGS & CustomItem::TOSS_ON_SPAWN || CUSTOM_ITEM_FLAGS & CustomItem::ENABLE_GRAVITY) {
actor->gravity = -1.4f;
}
Actor_ProcessInitChain(actor, sInitChain);
Collider_InitCylinder(play, &enItem00->collider);
Collider_SetCylinder(play, &enItem00->collider, actor, &sCylinderInit);
enItem00->unk_15A = -1;
}
// By default this will just assume the GID was passed in as the rot z, if you want different functionality you should
// override the draw
void CustomCollectible_Draw(Actor* actor, PlayState* play) {
void CustomItem_Draw(Actor* actor, PlayState* play) {
Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY);
GetItem_Draw(play, CUSTOM_ITEM_PARAM);
}
void CustomCollectible_Update(Actor* actor, PlayState* play) {
// Once the item is touched we need to clear movement vars so the item doesn't sink in the players hands/above head
void CustomItem_ItemTouched(Actor* actor, PlayState* play) {
actor->speedXZ = 0.0f;
actor->velocity.y = 0.0f;
actor->gravity = 0.0f;
actor->shape.yOffset = 1250.0f;
}
void CustomItem_Update(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
Player* player = GET_PLAYER(play);
if (!(CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_SPINNING)) {
if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_SPINNING)) {
actor->shape.rot.y += 960;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::STOP_BOBBING) {
actor->shape.yOffset = 1250.0f;
} else {
if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_BOBBING)) {
actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f;
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::HIDE_TILL_OVERHEAD) {
if (CUSTOM_ITEM_FLAGS & CustomItem::HIDE_TILL_OVERHEAD) {
Actor_SetScale(actor, 0.0f);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KEEP_ON_PLAYER) {
actor->gravity = 0.0f;
if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) {
Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
}
if (CUSTOM_ITEM_FLAGS & CustomCollectible::KILL_ON_TOUCH) {
// Player range check accounting for goron rolling behavior. Matches EnItem00 range check.
bool playerInRangeOfPickup = (actor->xzDistToPlayer <= 30.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(50.0f));
if (CUSTOM_ITEM_FLAGS & CustomItem::KILL_ON_TOUCH) {
// Pretty self explanatory, if the player is within range, kill the actor and call the action function
if ((actor->xzDistToPlayer <= 50.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) {
if (playerInRangeOfPickup) {
if (enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION;
}
Actor_Kill(actor);
}
} else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_OVERHEAD) {
} else if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_OVERHEAD) {
// If the item hasn't been picked up (unk_15A == -1) and the player is within range
if (enItem00->unk_15A == -1 && (actor->xzDistToPlayer <= 50.0f) &&
(fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) {
if (enItem00->unk_15A == -1 && playerInRangeOfPickup) {
// Fire the action function
if (enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION;
}
Sfx_PlaySfxCentered(NA_SE_SY_GET_ITEM);
// Set the unk_15A to 15, this indicates the item has been picked up and will start the overhead animation
enItem00->unk_15A = 15;
CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING;
CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER;
CUSTOM_ITEM_FLAGS |= CustomItem::STOP_BOBBING;
CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER;
CustomItem_ItemTouched(actor, play);
// Move to player right away on this frame
Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
}
// If the item has been picked up
@@ -128,8 +165,7 @@ void CustomCollectible_Update(Actor* actor, PlayState* play) {
enItem00->unk_15A--;
// Account for the different heights of the player forms
f32 height = 45.0f;
// TODO: Check for adult?
f32 height = LINK_IS_ADULT ? 60.0f : 45.0f;
// Bob the item up and down
actor->world.pos.y += (height + (Math_SinS(enItem00->unk_15A * 15000) * (enItem00->unk_15A * 0.3f)));
@@ -139,15 +175,20 @@ void CustomCollectible_Update(Actor* actor, PlayState* play) {
if (enItem00->unk_15A == 0) {
Actor_Kill(actor);
}
} else if (CUSTOM_ITEM_FLAGS & CustomCollectible::GIVE_ITEM_CUTSCENE) {
} else if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) {
// If the item hasn't been picked up and the player is within range
if (!Actor_HasParent(actor, play) && enItem00->unk_15A == -1) {
Actor_OfferGetItem(actor, play, GI_SHIP, 50.0f, 20.0f);
Actor_OfferGetItem(actor, play, GI_SHIP, 50.0f, 80.0f);
} else {
if (enItem00->unk_15A == -1) {
CUSTOM_ITEM_FLAGS |= CustomCollectible::STOP_BOBBING;
CUSTOM_ITEM_FLAGS |= CustomCollectible::KEEP_ON_PLAYER;
CUSTOM_ITEM_FLAGS |= CustomCollectible::HIDE_TILL_OVERHEAD;
// actor->shape.yOffset = 1250.0f;
CUSTOM_ITEM_FLAGS |= CustomItem::STOP_BOBBING;
// Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos);
CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER;
// Actor_SetScale(actor, 0.0f);
CUSTOM_ITEM_FLAGS |= CustomItem::HIDE_TILL_OVERHEAD;
CustomItem_ItemTouched(actor, play);
}
// Begin incrementing the unk_15A, indicating the item has been picked up
@@ -158,14 +199,14 @@ void CustomCollectible_Update(Actor* actor, PlayState* play) {
// After the first 20 frames, show the item and call the action function
if (enItem00->unk_15A == 20 && enItem00->actionFunc != NULL) {
enItem00->actionFunc(enItem00, play);
CUSTOM_ITEM_FLAGS |= CustomCollectible::CALLED_ACTION;
CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION;
}
// Override the bobbing animation to be a fixed height
actor->shape.yOffset = 900.0f;
Actor_SetScale(actor, 0.007f);
f32 height = 45.0f;
// TODO: Check for adult?
// Account for the different heights of the player forms
f32 height = LINK_IS_ADULT ? 60.0f : 45.0f;
actor->world.pos.y += height;
}
@@ -186,11 +227,19 @@ void CustomCollectible_Update(Actor* actor, PlayState* play) {
actor->speedXZ = 0.0f;
}
Collider_UpdateCylinder(actor, &enItem00->collider);
CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base);
if (CUSTOM_ITEM_FLAGS & CustomItem::ABLE_TO_BOOMERANG) {
Collider_UpdateCylinder(actor, &enItem00->collider);
CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base);
}
}
void CustomCollectible::RegisterHooks() {
void CustomItem_Destroy(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
Collider_DestroyCylinder(play, &enItem00->collider);
}
void CustomItem::RegisterHooks() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::ShouldActorInit>(
ACTOR_EN_ITEM00, [](void* actorRef, bool* should) {
Actor* actor = (Actor*)actorRef;
@@ -198,13 +247,16 @@ void CustomCollectible::RegisterHooks() {
return;
}
actor->init = CustomCollectible_Init;
actor->update = CustomCollectible_Update;
actor->draw = CustomCollectible_Draw;
actor->destroy = NULL;
actor->init = CustomItem_Init;
actor->update = CustomItem_Update;
actor->draw = CustomItem_Draw;
actor->destroy = CustomItem_Destroy;
// Set the rotX/rotZ back to 0, the original values can be accessed from actor->home
actor->world.rot.x = 0;
actor->world.rot.z = 0;
actor->shape.rot.x = 0;
actor->shape.rot.y = 0;
actor->shape.rot.z = 0;
});
}
@@ -1,3 +1,6 @@
#ifndef CUSTOM_ITEM_H
#define CUSTOM_ITEM_H
extern "C" {
#include "z64actor.h"
}
@@ -5,9 +8,9 @@ extern "C" {
#define CUSTOM_ITEM_FLAGS (actor->home.rot.x)
#define CUSTOM_ITEM_PARAM (actor->home.rot.z)
namespace CustomCollectible {
namespace CustomItem {
enum CustomCollectibleFlags : int16_t {
enum CustomItemFlags : int16_t {
KILL_ON_TOUCH = 1 << 0, // 0000 0000 0000 0001
GIVE_OVERHEAD = 1 << 1, // 0000 0000 0000 0010
GIVE_ITEM_CUTSCENE = 1 << 2, // 0000 0000 0000 0100
@@ -17,8 +20,12 @@ enum CustomCollectibleFlags : int16_t {
STOP_SPINNING = 1 << 6, // 0000 0000 0100 0000
CALLED_ACTION = 1 << 7, // 0000 0000 1000 0000
TOSS_ON_SPAWN = 1 << 8, // 0000 0001 0000 0000
ABLE_TO_BOOMERANG = 1 << 9, // 0000 0010 0000 0000
ENABLE_GRAVITY = 1 << 10, // 0000 0100 0000 0000
};
void RegisterHooks();
EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL,
ActorFunc drawFunc = NULL);
}; // namespace CustomCollectible
}; // namespace CustomItem
#endif // CUSTOM_ITEM_H
@@ -1,5 +1,7 @@
#include "CustomMessageManager.h"
#include "CustomMessageInterfaceAddon.h"
#include "CustomMessageTypes.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include <algorithm>
#include <stdint.h>
#include <cstring>
@@ -10,6 +12,8 @@
#include "soh/util.h"
extern "C" {
#include "functions.h"
extern PlayState* gPlayState;
}
@@ -223,8 +227,20 @@ void CustomMessage::LoadIntoFont() {
Font* font = &msgCtx->font;
char* buffer = font->msgBuf;
const int maxBufferSize = sizeof(font->msgBuf);
font->charTexBuf[0] = (type << 4) | position;
msgCtx->msgLength = font->msgLength = SohUtils::CopyStringToCharBuffer(GetEnglish(MF_RAW), buffer, maxBufferSize);
std::string content = GetEnglish(MF_RAW);
switch (gSaveContext.language) {
case LANGUAGE_FRA:
content = GetFrench(MF_RAW);
break;
case LANGUAGE_GER:
content = GetGerman(MF_RAW);
break;
}
msgCtx->msgLength = font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, content, maxBufferSize);
}
CustomMessage CustomMessage::operator+(const CustomMessage& right) const {
@@ -836,3 +852,21 @@ bool CustomMessageManager::AddCustomMessageTable(std::string tableID) {
CustomMessageTable newMessageTable;
return messageTables.emplace(tableID, newMessageTable).second;
}
void CustomMessageManager::SetActiveCustomMessage(CustomMessage message) {
activeCustomMessage = message;
}
void CustomMessageManager::StartTextbox(CustomMessage message) {
activeCustomMessage = message;
Message_StartTextbox(gPlayState, TEXT_CUSTOM_MESSAGE, &GET_PLAYER(gPlayState)->actor);
}
void CustomMessageManager::RegisterHooks() {
GameInteractor::Instance->RegisterGameHookForID<GameInteractor::OnOpenText>(
TEXT_CUSTOM_MESSAGE, [&](u16* textId, bool* loadFromMessageTable) {
*loadFromMessageTable = false;
activeCustomMessage.LoadIntoFont();
});
}
@@ -247,11 +247,15 @@ class CustomMessageManager {
bool InsertCustomMessage(std::string tableID, uint16_t textID, CustomMessage message);
CustomMessage activeCustomMessage;
public:
static CustomMessageManager* Instance;
CustomMessageManager() = default;
void RegisterHooks();
/**
* @brief Formats the provided Custom Message Entry and inserts it into the table with the provided tableID,
* with the provided giid (getItemID) as its key. This function also inserts the icon corresponding to
@@ -310,6 +314,22 @@ class CustomMessageManager {
* already exists.)
*/
bool AddCustomMessageTable(std::string tableID);
/**
* @brief Sets the active custom message, which will be used the next time
* TEXT_CUSTOM_MESSAGE is used for a text box.
*
* @param message the message to set as active
*/
void SetActiveCustomMessage(CustomMessage message);
/**
* @brief Displays a custom message in a textbox. This is the same as calling
* SetActiveCustomMessage and then beginning a textbox with TEXT_CUSTOM_MESSAGE.
*
* @param message the message to set as active
*/
void StartTextbox(CustomMessage message);
};
class MessageNotFoundException : public std::exception {
@@ -14,6 +14,7 @@ typedef enum {
TEXT_SKULLTULA_PEOPLE_MAKE_YOU_VERY_RICH = 0x0027,
TEXT_SKULLTULA_PEOPLE_CURSE_HAS_BEEN_BROKEN = 0x0028,
TEXT_SKULLTULA_PEOPLE_SAVING_MY_KIDS = 0x0029,
TEXT_CUSTOM_MESSAGE = 0x004B, // Unused
TEXT_ITEM_KEY_SMALL = 0x0060,
TEXT_ITEM_DUNGEON_MAP = 0x0066,
TEXT_CHEST_GAME_REAL_GAMBLER = 0x006E,
@@ -0,0 +1,95 @@
#include "GameInteractor.h"
#include "soh/Enhancements/custom-item/CustomItem.h"
extern "C" {
#include "variables.h"
#include "macros.h"
#include "functions.h"
extern SaveContext gSaveContext;
extern PlayState* gPlayState;
}
void ProcessEvents() {
Player* player = GET_PLAYER(gPlayState);
// If the player has a message active, stop
if (gPlayState->msgCtx.msgMode != 0) {
return;
}
// If the player is in a blocking cutscene, stop
if (Player_InBlockingCsMode(gPlayState, player)) {
return;
}
// If player is dead, stop
if (player->stateFlags1 & PLAYER_STATE1_DEAD) {
return;
}
// If there is an event active, stop
const auto& currentEvent = GameInteractor::Instance->currentEvent;
if (auto e = std::get_if<GIEventNone>(&currentEvent)) {
// no-op
} else {
return;
}
// If there are no events that need to happen, stop
if (GameInteractor::Instance->events.empty()) {
return;
}
GameInteractor::Instance->currentEvent = GameInteractor::Instance->events.front();
const auto& nextEvent = GameInteractor::Instance->currentEvent;
if (auto e = std::get_if<GIEventGiveItem>(&nextEvent)) {
EnItem00* enItem00;
// If the player is climbing or in the air, deliver the item without a cutscene but freeze the player
if (!e->showGetItemCutscene ||
(player->stateFlags1 &
(PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE |
PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON |
PLAYER_STATE1_CLIMBING_LADDER | PLAYER_STATE1_IN_WATER)) ||
(Player_GetExplosiveHeld(player) > -1)) {
enItem00 = CustomItem::Spawn(
player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0,
CustomItem::GIVE_OVERHEAD | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param,
[](Actor* actor, PlayState* play) {
Player* player = GET_PLAYER(gPlayState);
const auto& nextEvent = GameInteractor::Instance->currentEvent;
if (auto e = std::get_if<GIEventGiveItem>(&nextEvent)) {
e->giveItem(actor, play);
if (e->showGetItemCutscene) {
player->actor.freezeTimer = 30;
}
}
},
e->drawItem);
} else {
enItem00 = CustomItem::Spawn(
player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0,
CustomItem::GIVE_ITEM_CUTSCENE | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param,
e->giveItem, e->drawItem);
}
enItem00->actor.destroy = [](Actor* actor, PlayState* play) {
if (!(CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION)) {
// Event was not handled, requeue it
GameInteractor::Instance->events.push_back(GameInteractor::Instance->currentEvent);
}
GameInteractor::Instance->currentEvent = GIEventNone{};
};
}
GameInteractor::Instance->events.erase(GameInteractor::Instance->events.begin());
}
void GameInteractor::RegisterOwnHooks() {
// Cleanup all hooks at the start of each frame
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameStateMainStart>(
[]() { GameInteractor::Instance->RemoveAllQueuedHooks(); });
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayerUpdate>(ProcessEvents);
}
@@ -109,6 +109,22 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state);
#pragma message("Compiling without <source_location> support, the Hook Debugger will not be available")
#endif
struct GIEventNone {};
struct GIEventGiveItem {
// Whether or not to show the get item cutscene. If true and the player is in the air, the
// player will instead be frozen for a few seconds. If this is true you _must_ call
// CustomMessage::SetActiveCustomMessage in the giveItem function otherwise you'll just see a blank message.
bool showGetItemCutscene;
// Arbitrary s16 that can be accessed from within the give/draw functions with CUSTOM_ITEM_PARAM
s16 param;
// These are run in the context of an item00 actor. This isn't super important but can be useful in some cases
ActorFunc giveItem;
ActorFunc drawItem;
};
typedef std::variant<GIEventNone, GIEventGiveItem> GIEvent;
typedef uint32_t HOOK_ID;
enum HookType {
@@ -221,6 +237,8 @@ class GameInteractor {
public:
static GameInteractor* Instance;
void RegisterOwnHooks();
// Game State
class State {
public:
@@ -253,6 +271,10 @@ class GameInteractor {
static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect);
static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect);
// EventQueue
std::vector<GIEvent> events = {};
GIEvent currentEvent = GIEventNone();
// Game Hooks
HOOK_ID nextHookId = 1;
@@ -17,6 +17,7 @@ DEFINE_HOOK(OnEquipmentDelete, (int16_t equipmentType, uint16_t equipValue));
DEFINE_HOOK(OnSaleEnd, (GetItemEntry itemEntry));
DEFINE_HOOK(OnTransitionEnd, (int16_t sceneNum));
DEFINE_HOOK(OnSceneInit, (int16_t sceneNum));
DEFINE_HOOK(OnRoomInit, (int16_t roomNum));
DEFINE_HOOK(AfterSceneCommands, (int16_t sceneNum));
DEFINE_HOOK(OnSceneFlagSet, (int16_t sceneNum, int16_t flagType, int16_t flag));
DEFINE_HOOK(OnSceneFlagUnset, (int16_t sceneNum, int16_t flagType, int16_t flag));
@@ -23,9 +23,6 @@ void GameInteractor_ExecuteOnExitGame(int32_t fileNum) {
}
void GameInteractor_ExecuteOnGameStateMainStart() {
// Cleanup all hooks at the start of each frame
GameInteractor::Instance->RemoveAllQueuedHooks();
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameStateMainStart>();
}
@@ -60,6 +57,12 @@ void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum) {
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnSceneInit>(sceneNum);
}
void GameInteractor_ExecuteOnRoomInit(int16_t roomNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnRoomInit>(roomNum);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnRoomInit>(roomNum, roomNum);
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnRoomInit>(roomNum);
}
void GameInteractor_ExecuteAfterSceneCommands(int16_t sceneNum) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::AfterSceneCommands>(sceneNum);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::AfterSceneCommands>(sceneNum, sceneNum);
@@ -20,6 +20,7 @@ void GameInteractor_ExecuteOnEquipmentDelete(int16_t equipmentType, uint16_t equ
void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry);
void GameInteractor_ExecuteOnTransitionEndHooks(int16_t sceneNum);
void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum);
void GameInteractor_ExecuteOnRoomInit(int16_t roomNum);
void GameInteractor_ExecuteAfterSceneCommands(int16_t sceneNum);
void GameInteractor_ExecuteOnSceneFlagSet(int16_t sceneNum, int16_t flagType, int16_t flag);
void GameInteractor_ExecuteOnSceneFlagUnset(int16_t sceneNum, int16_t flagType, int16_t flag);
@@ -1761,6 +1761,14 @@ typedef enum {
// - `*ObjTsubo`
VB_POT_DROP_ITEM,
// #### `result`
// ```c
// actor.params & 1) == ROCK_SMALL
// ```
// #### `args`
// - `*EnIshi`
VB_ROCK_DROP_ITEM,
// #### `result`
// ```c
// true
@@ -2252,6 +2260,16 @@ typedef enum {
// - `*Actor`
VB_RECIEVE_FALL_DAMAGE,
// #### `result`
// ```c
// true
// ```
// #### `args`
// - `*Actor`
// - `u8` (damageEffect)
// - `u8` (damage)
VB_APPLY_DAMAGE_TO_ACTOR,
// #### `result`
// ```c
// true
@@ -946,7 +946,7 @@ std::string CleanCheckConditionString(std::string condition) {
}
namespace Regions {
auto GetAllRegions() {
std::array<RandomizerRegion, RR_MAX - (RR_NONE + 1)> GetAllRegions() {
static const size_t regionCount = RR_MAX - (RR_NONE + 1);
static std::array<RandomizerRegion, regionCount> allRegions = {};
@@ -4,6 +4,7 @@
#include <vector>
#include <list>
#include <set>
#include <array>
#include "soh/Enhancements/randomizer/randomizerTypes.h"
#include "soh/Enhancements/randomizer/context.h"
@@ -237,6 +238,7 @@ extern void AccessReset();
extern void ResetAllLocations();
extern bool HasTimePassAccess(uint8_t age);
extern void DumpWorldGraph(std::string str);
extern std::array<RandomizerRegion, RR_MAX - (RR_NONE + 1)> GetAllRegions();
} // namespace Regions
void RegionTable_Init();
+40 -6
View File
@@ -40,6 +40,26 @@ void Anchor::OnDisconnected() {
RegisterHooks();
}
void Anchor::ProcessOutgoingPackets() {
// Copy all queued packets while holding the lock, then send them after releasing
std::queue<nlohmann::json> packetsToSend;
{
std::lock_guard<std::mutex> lock(outgoingPacketQueueMutex);
packetsToSend.swap(outgoingPacketQueue);
}
// Send packets without holding the lock
while (!packetsToSend.empty()) {
nlohmann::json payload = packetsToSend.front();
packetsToSend.pop();
if (!payload.contains("quiet")) {
SPDLOG_DEBUG("[Anchor] Sending payload:\n{}", payload.dump());
}
Network::SendJsonToRemote(payload);
}
}
void Anchor::SendJsonToRemote(nlohmann::json payload) {
if (!isConnected) {
return;
@@ -47,9 +67,17 @@ void Anchor::SendJsonToRemote(nlohmann::json payload) {
payload["clientId"] = ownClientId;
if (!payload.contains("quiet")) {
SPDLOG_DEBUG("[Anchor] Sending payload:\n{}", payload.dump());
SPDLOG_DEBUG("[Anchor] Queuing payload:\n{}", payload.dump());
}
Network::SendJsonToRemote(payload);
if (payload["type"] == HANDSHAKE) {
Network::SendJsonToRemote(payload);
return;
}
// Queue the packet to be sent on the network thread
std::lock_guard<std::mutex> lock(outgoingPacketQueueMutex);
outgoingPacketQueue.push(payload);
}
void Anchor::OnIncomingJson(nlohmann::json payload) {
@@ -87,11 +115,17 @@ void Anchor::OnIncomingJson(nlohmann::json payload) {
}
void Anchor::ProcessIncomingPacketQueue() {
std::lock_guard<std::mutex> lock(incomingPacketQueueMutex);
// Copy all queued packets while holding the lock, then process them after releasing
std::queue<nlohmann::json> packetsToProcess;
{
std::lock_guard<std::mutex> lock(incomingPacketQueueMutex);
packetsToProcess.swap(incomingPacketQueue);
}
while (!incomingPacketQueue.empty()) {
nlohmann::json payload = incomingPacketQueue.front();
incomingPacketQueue.pop();
// Process packets without holding the lock
while (!packetsToProcess.empty()) {
nlohmann::json payload = packetsToProcess.front();
packetsToProcess.pop();
std::string packetType = payload["type"].get<std::string>();
+3
View File
@@ -76,6 +76,8 @@ class Anchor : public Network {
bool isProcessingIncomingPacket = false;
std::queue<nlohmann::json> incomingPacketQueue;
std::mutex incomingPacketQueueMutex;
std::queue<nlohmann::json> outgoingPacketQueue;
std::mutex outgoingPacketQueueMutex;
nlohmann::json PrepClientState();
nlohmann::json PrepRoomState();
@@ -143,6 +145,7 @@ class Anchor : public Network {
void OnIncomingJson(nlohmann::json payload);
void OnConnected();
void OnDisconnected();
void ProcessOutgoingPackets();
void DrawMenu();
void ProcessIncomingPacketQueue();
void SendJsonToRemote(nlohmann::json packet);
+14
View File
@@ -46,6 +46,9 @@ void Network::OnConnected() {
void Network::OnDisconnected() {
}
void Network::ProcessOutgoingPackets() {
}
void Network::SendDataToRemote(const char* payload) {
#ifdef ENABLE_REMOTE_CONTROL
SPDLOG_DEBUG("[Network] Sending data: {}", payload);
@@ -68,6 +71,7 @@ void Network::ReceiveFromServer() {
if (networkSocket) {
isConnected = true;
receivedData.clear();
SPDLOG_INFO("[Network] Connection to server established!");
OnConnected();
@@ -90,7 +94,11 @@ void Network::ReceiveFromServer() {
break;
}
// Always process outgoing packets
ProcessOutgoingPackets();
if (socketsReady == 0) {
// No incoming data
continue;
}
@@ -119,9 +127,15 @@ void Network::ReceiveFromServer() {
}
}
if (socketSet) {
SDLNet_FreeSocketSet(socketSet);
}
if (isConnected) {
SDLNet_TCP_Close(networkSocket);
networkSocket = nullptr;
isConnected = false;
receivedData.clear();
OnDisconnected();
SPDLOG_INFO("[Network] Ending receiving thread...");
}
+1
View File
@@ -44,6 +44,7 @@ class Network {
virtual void OnIncomingJson(nlohmann::json payload);
virtual void OnConnected();
virtual void OnDisconnected();
virtual void ProcessOutgoingPackets();
void SendDataToRemote(const char* payload);
virtual void SendJsonToRemote(nlohmann::json packet);
};
+21 -20
View File
@@ -82,7 +82,6 @@
#include "Enhancements/mods.h"
#include "Enhancements/game-interactor/GameInteractor.h"
#include "Enhancements/randomizer/draw.h"
#include "Enhancements/custom-collectible/CustomCollectible.h"
#include <libultraship/libultraship.h>
#include <libultraship/controller/controldeck/ControlDeck.h>
#include <fast/resource/ResourceType.h>
@@ -129,6 +128,7 @@
#include "soh/config/ConfigUpdaters.h"
#include "soh/ShipInit.hpp"
#include "soh/Enhancements/custom-item/CustomItem.h"
extern "C" {
#include "src/overlays/actors/ovl_En_Dns/z_en_dns.h"
@@ -769,9 +769,9 @@ extern "C" void VanillaItemTable_Init() {
GET_ITEM(ITEM_NUT_UPGRADE_30, OBJECT_GI_NUTS, GID_NUTS, 0xA7, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_30),
GET_ITEM(ITEM_NUT_UPGRADE_40, OBJECT_GI_NUTS, GID_NUTS, 0xA8, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_NONE, GI_NUT_UPGRADE_40),
GET_ITEM(ITEM_BULLET_BAG_50, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG_50, 0x6C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_50),
GET_ITEM(ITEM_SHIP, OBJECT_UNSET_16E, GID_MAXIMUM, 0x00, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_SHIP),
GET_ITEM_NONE,
GET_ITEM_NONE,
GET_ITEM(ITEM_SHIP, OBJECT_UNSET_16E, GID_MAXIMUM,TEXT_CUSTOM_MESSAGE, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_SHIP),
GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry.
// clang-format on
};
@@ -1282,6 +1282,22 @@ extern "C" void InitOTR(int argc, char* argv[]) {
conf->RegisterVersionUpdater(std::make_shared<SOH::ConfigVersion4Updater>());
conf->RunVersionUpdates();
CVarRegisterInteger(CVAR_SETTING("AltAssets"), 1);
CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1);
CVarRegisterInteger("gHoliday.Visual.SnowingWeather", 1);
CVarRegisterInteger("gHoliday.Visual.Hats", 1);
CVarRegisterInteger("gHoliday.Gameplay.Snowballs", 1);
CVarRegisterInteger(CVAR_COSMETIC("Hud.AButton.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.AButton.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Hud.BButton.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.BButton.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Hud.CButtons.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.CButtons.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Consumable.Hearts.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Consumable.Hearts.Value"), Color_RGBA8{ 255, 158, 0, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Consumable.Magic.Value"), Color_RGBA8{ 255, 0, 0, 255 });
SohGui::SetupGuiElements();
ShipInit::InitAll();
@@ -1309,9 +1325,11 @@ extern "C" void InitOTR(int argc, char* argv[]) {
OTRExtScanner();
VanillaItemTable_Init();
DebugConsole_Init();
CustomMessageManager::Instance->RegisterHooks();
GameInteractor::Instance->RegisterOwnHooks();
CustomItem::RegisterHooks();
InitMods();
CustomCollectible::RegisterHooks();
ActorDB::AddBuiltInCustomActors();
// #region SOH [Randomizer] TODO: Remove these and refactor spoiler file handling for randomizer
CVarClear(CVAR_GENERAL("RandomizerNewFileDropped"));
@@ -1324,23 +1342,6 @@ extern "C" void InitOTR(int argc, char* argv[]) {
time_t now = time(NULL);
tm* tm_now = localtime(&now);
// if (tm_now->tm_mon == 11 && tm_now->tm_mday >= 24 && tm_now->tm_mday <= 25) {
// CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1);
// } else {
// CVarClear(CVAR_GENERAL("LetItSnow"));
// }
CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1);
CVarRegisterInteger(CVAR_COSMETIC("Hud.AButton.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.AButton.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Hud.BButton.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.BButton.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Hud.CButtons.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Hud.CButtons.Value"), Color_RGBA8{ 255, 255, 255, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Consumable.Hearts.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Consumable.Hearts.Value"), Color_RGBA8{ 255, 158, 0, 255 });
CVarRegisterInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 1);
CVarRegisterColor(CVAR_COSMETIC("Consumable.Magic.Value"), Color_RGBA8{ 255, 0, 0, 255 });
srand(now);
#ifdef ENABLE_REMOTE_CONTROL
+44
View File
@@ -114,6 +114,7 @@ SaveManager::SaveManager() {
coreSectionIDsByName["scenes"] = SECTION_ID_SCENES;
coreSectionIDsByName["trackerData"] = SECTION_ID_TRACKER_DATA;
coreSectionIDsByName["archipelagoData"] = SECTION_ID_ARCHIPELAGO;
coreSectionIDsByName["rogueLike"] = SECTION_ID_ROGUELIKE;
AddLoadFunction("base", 1, LoadBaseVersion1);
AddLoadFunction("base", 2, LoadBaseVersion2);
AddLoadFunction("base", 3, LoadBaseVersion3);
@@ -122,6 +123,8 @@ SaveManager::SaveManager() {
AddLoadFunction("randomizer", 1, LoadRandomizer);
AddSaveFunction("randomizer", 1, SaveRandomizer, true, SECTION_PARENT_NONE);
AddLoadFunction("rogueLike", 1, LoadRogueLike);
AddSaveFunction("rogueLike", 1, SaveRogueLike, true, SECTION_PARENT_NONE);
AddInitFunction(InitFileImpl);
@@ -412,6 +415,36 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f
});
}
void SaveManager::LoadRogueLike() {
if (gSaveContext.ship.quest.id != QUEST_ROGUELIKE) {
return;
}
SaveManager::Instance->LoadData("difficulty", gSaveContext.ship.quest.data.rogueLike.difficulty);
SaveManager::Instance->LoadData("lastActivity", gSaveContext.ship.quest.data.rogueLike.lastActivity);
SaveManager::Instance->LoadData("xp", gSaveContext.ship.quest.data.rogueLike.xp);
SaveManager::Instance->LoadArray("stats", ARRAY_COUNT(gSaveContext.ship.quest.data.rogueLike.stats), [&](size_t i) {
u32 value = 0;
SaveManager::Instance->LoadData("", value);
gSaveContext.ship.quest.data.rogueLike.stats[i] = value;
});
}
void SaveManager::SaveRogueLike(SaveContext* saveContext, int sectionID, bool fullSave) {
if (saveContext->ship.quest.id != QUEST_ROGUELIKE) {
return;
}
SaveManager::Instance->SaveData("difficulty", saveContext->ship.quest.data.rogueLike.difficulty);
SaveManager::Instance->SaveData("lastActivity", saveContext->ship.quest.data.rogueLike.lastActivity);
SaveManager::Instance->SaveData("xp", saveContext->ship.quest.data.rogueLike.xp);
SaveManager::Instance->SaveArray("stats", ARRAY_COUNT(saveContext->ship.quest.data.rogueLike.stats), [&](size_t i) {
SaveManager::Instance->SaveData("", saveContext->ship.quest.data.rogueLike.stats[i]);
});
}
// Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any
// class initialization stuff here
void SaveManager::Init() {
@@ -623,6 +656,11 @@ void SaveManager::InitMeta(int fileNum) {
fileMetaInfo[fileNum].requiresOriginal =
!IS_MASTER_QUEST && (!IS_RANDO || randoContext->GetDungeons()->CountMQ() < 12);
if (IS_ROGUELIKE) { // IDK
fileMetaInfo[fileNum].requiresMasterQuest = false;
fileMetaInfo[fileNum].requiresOriginal = false;
}
fileMetaInfo[fileNum].buildVersionMajor = gSaveContext.ship.stats.buildVersionMajor;
fileMetaInfo[fileNum].buildVersionMinor = gSaveContext.ship.stats.buildVersionMinor;
fileMetaInfo[fileNum].buildVersionPatch = gSaveContext.ship.stats.buildVersionPatch;
@@ -2148,6 +2186,9 @@ void SaveManager::LoadBaseVersion4() {
SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams);
SaveManager::Instance->LoadData("filenameLanguage", gSaveContext.ship.filenameLanguage);
SaveManager::Instance->LoadData("maskMemory", gSaveContext.ship.maskMemory);
// Ugh..
SaveManager::Instance->LoadData("questId", gSaveContext.ship.quest.id);
}
void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSave) {
@@ -2316,6 +2357,9 @@ void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSav
SaveManager::Instance->SaveData("dogParams", saveContext->dogParams);
SaveManager::Instance->SaveData("filenameLanguage", saveContext->ship.filenameLanguage);
SaveManager::Instance->SaveData("maskMemory", saveContext->ship.maskMemory);
// Ugh..
SaveManager::Instance->SaveData("questId", gSaveContext.ship.quest.id);
}
// Load a string into a char array based on size and ensuring it is null terminated when overflowed
+2
View File
@@ -174,6 +174,8 @@ class SaveManager {
static void LoadRandomizer();
static void SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave);
static void LoadRogueLike();
static void SaveRogueLike(SaveContext* saveContext, int sectionID, bool fullSave);
static void LoadBaseVersion1();
static void LoadBaseVersion2();
+2
View File
@@ -87,6 +87,8 @@ void SohMenu::InitElement() {
AddMenuEnhancements();
AddMenuRandomizer();
AddMenuEntry("Holiday", CVAR_SETTING("Menu.HolidaySidebarSection"));
AddSidebarEntry("Holiday", "Gameplay", 2);
AddSidebarEntry("Holiday", "Visual", 2);
AddMenuNetwork();
AddMenuDevTools();
+1
View File
@@ -517,6 +517,7 @@ extern "C" s32 OTRfunc_8009728C(PlayState* play, RoomContext* roomCtx, s32 roomN
roomCtx->unk_30 ^= 1;
SPDLOG_INFO("Room Init - curRoom.num: {0:#x}", roomCtx->curRoom.num);
GameInteractor_ExecuteOnRoomInit(roomCtx->curRoom.num);
return 1;
}
+1 -1
View File
@@ -3353,7 +3353,7 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos
objBankIndex = Object_GetIndex(&gPlayState->objectCtx, dbEntry->objectId);
if (objBankIndex < 0 && (!gMapLoading || CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) ||
CVarGetInteger("gHoliday.Caladius.Blitz.Enabled", 0))) {
CVarGetInteger("gHoliday.Gameplay.Blitz.Enabled", 0))) {
objBankIndex = 0;
}
+8
View File
@@ -2,6 +2,7 @@
#include "vt.h"
#include "overlays/effects/ovl_Effect_Ss_HitMark/z_eff_ss_hitmark.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include <assert.h>
typedef s32 (*ColChkResetFunc)(PlayState*, Collider*);
@@ -3030,6 +3031,13 @@ void CollisionCheck_ApplyDamage(PlayState* play, CollisionCheckContext* colChkCt
if (CVarGetInteger(CVAR_ENHANCEMENT("IvanCoopModeEnabled"), 0)) {
collider->actor->colChkInfo.damage *= GET_PLAYER(play)->ivanDamageMultiplier;
}
if (!GameInteractor_Should(VB_APPLY_DAMAGE_TO_ACTOR, true, collider->actor,
collider->actor->colChkInfo.damageEffect, collider->actor->colChkInfo.damage,
info->acHitInfo->toucher.dmgFlags)) {
collider->actor->colChkInfo.damageEffect = 0;
collider->actor->colChkInfo.damage = 0;
}
}
/**
+3 -2
View File
@@ -399,9 +399,10 @@ DrawItemTableEntry sDrawItemTable[] = {
* Calls the corresponding draw function for the given draw ID
*/
void GetItem_Draw(PlayState* play, s16 drawId) {
if (drawId < 0 || drawId >= GID_MAXIMUM) {
// SoH [Enhancements] Prevent any UB here, GID_MAXIMUM useful for overriding GI draws
if (drawId < 0 || drawId >= GID_MAXIMUM)
return;
}
sDrawItemTable[drawId].drawFunc(play, drawId);
}
+2
View File
@@ -2716,6 +2716,8 @@ void Message_OpenText(PlayState* play, u16 textId) {
bool loadFromMessageTable = true;
GameInteractor_ExecuteOnOpenText(&textId, &loadFromMessageTable);
sDisplayNextMessageAsEnglish = false;
if (msgCtx->msgMode == MSGMODE_NONE) {
gSaveContext.unk_13EE = gSaveContext.unk_13EA;
}
+4 -7
View File
@@ -1873,11 +1873,9 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) {
* @return u8
*/
u8 Item_Give(PlayState* play, u8 item) {
// TODO: Add ShouldItemGive
// if (!GameInteractor_ShouldItemGive(item) || item == ITEM_SHIP) {
if (item == ITEM_SHIP) {
// SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem
if (item == ITEM_SHIP)
return ITEM_NONE;
}
// prevents getting sticks without the bag in case something got missed
if (IS_RANDO && (item == ITEM_STICK || item == ITEM_STICKS_5 || item == ITEM_STICKS_10) &&
@@ -2479,10 +2477,9 @@ u8 Item_CheckObtainability(u8 item) {
s16 slot = SLOT(item);
s32 temp;
// SOH [Enhancements] Added to enable custom item gives
if (item == ITEM_SHIP) {
// SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem
if (item == ITEM_SHIP)
return ITEM_NONE;
}
if (item >= ITEM_STICKS_5) {
slot = SLOT(sExtraItemBases[item - ITEM_STICKS_5]);
+5 -1
View File
@@ -1603,6 +1603,10 @@ void Play_Draw(PlayState* play) {
if ((HREG(80) != 10) || (HREG(88) != 0)) {
if (play->envCtx.sandstormState != SANDSTORM_OFF) {
Environment_DrawSandstorm(play, play->envCtx.sandstormState);
} else if (CVarGetInteger("gHoliday.Visual.SnowingWeatherActive", 0) == 1) {
Environment_DrawSandstorm(play, SANDSTORM_DISSIPATE);
} else if (CVarGetInteger("gHoliday.Visual.SnowingWeatherActive", 0) == 2) {
Environment_DrawSandstorm(play, SANDSTORM_ACTIVE);
}
}
@@ -1699,7 +1703,7 @@ time_t Play_GetRealTime() {
void Play_Main(GameState* thisx) {
PlayState* play = (PlayState*)thisx;
if (play->envCtx.unk_EE[2] == 0 && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (play->envCtx.unk_EE[2] == 0 && CVarGetInteger("gHoliday.Visual.SnowingWeather", 0)) {
play->envCtx.unk_EE[3] = 64;
Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_OBJECT_KANKYO, 0, 0, 0, 0, 0, 0, 3, 0);
}
+1 -1
View File
@@ -1814,7 +1814,7 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve
Matrix_MultVec3f(&sZeroVec, D_80160000);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0) && !(this->stateFlags1 & PLAYER_STATE1_FIRST_PERSON) &&
if (CVarGetInteger("gHoliday.Visual.Hats", 0) && !(this->stateFlags1 & PLAYER_STATE1_FIRST_PERSON) &&
!(this->stateFlags2 & PLAYER_STATE2_CRAWLING)) {
if (limbIndex == PLAYER_LIMB_HEAD) {
OPEN_DISPS(play->state.gfxCtx);
@@ -1359,7 +1359,7 @@ void BossDodongo_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s
}
Collider_UpdateSpheres(limbIndex, &this->collider);
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 7) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -2650,7 +2650,7 @@ void BossGanon2_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s*
}
}
if (CVarGetInteger("gLetItSnow", 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 16) {
Matrix_Push();
Matrix_RotateZYX(5977, 4649, 18154, MTXMODE_APPLY);
@@ -2782,7 +2782,7 @@ void BossGanon2_PostLimbDraw2(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s
Matrix_MultVec3f(&D_80907164, &this->unk_1B8);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 11) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -1436,7 +1436,7 @@ void BossGanondrof_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec
Matrix_MultVec3f(&zeroVec, &this->bodyPartsPos[limbIndex - 1]);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0) && this->deathState == NOT_DEAD) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0) && this->deathState == NOT_DEAD) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
+1 -1
View File
@@ -956,7 +956,7 @@ void EnAm_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
EnAm_TransformSwordHitbox(&this->dyna.actor, play);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 4) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -320,7 +320,7 @@ void EnAni_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&sMultVec, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
+1 -1
View File
@@ -1274,7 +1274,7 @@ void EnBb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
BodyBreak_SetInfo(&this->bodyBreak, limbIndex, 4, 15, 15, dList, BODYBREAK_OBJECT_DEFAULT);
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -528,7 +528,7 @@ void EnBomChu_Draw(Actor* thisx, PlayState* play) {
gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_OPA_DISP++, gBombchuDL);
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
Matrix_Push();
Matrix_RotateZYX(0, -3100, 17047, MTXMODE_APPLY);
Matrix_Translate(445.946f, -27.027f, 608.108f, MTXMODE_APPLY);
@@ -674,7 +674,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) {
}
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0) && hasChristmasChestTexturesAvailable &&
if (CVarGetInteger("gHoliday.Visual.PresentChests", 0) && hasChristmasChestTexturesAvailable &&
hasCreatedRandoChestTextures && !hasCustomChestDLs) {
if (this->dyna.actor.scale.x == 0.01f) {
this->boxBodyDL = gChristmasRedTreasureChestChestFrontDL;
+1 -1
View File
@@ -524,7 +524,7 @@ void EnCs_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_Get(&this->spookyMaskMtx);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -539,7 +539,7 @@ void EnDaikuKakariko_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, V
gSPDisplayList(POLY_OPA_DISP++, carpenterHeadDLists[this->actor.params & 3]);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
Matrix_Push();
switch (this->actor.params) {
@@ -1278,7 +1278,7 @@ void EnDekubaba_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s*
Collider_UpdateSpheres(limbIndex, &this->collider);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 4) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -494,7 +494,7 @@ void EnDns_Update(Actor* thisx, PlayState* play) {
void EnDns_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnDns* this = (EnDns*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 17) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -916,7 +916,7 @@ void EnDodongo_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s*
}
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 7) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -497,7 +497,7 @@ s32 EnDog_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p
}
void EnDog_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 4) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
+1 -1
View File
@@ -291,7 +291,7 @@ void EnDs_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&sMultVec, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 5) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -656,20 +656,6 @@ void EnDu_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
if (limbIndex == 16) {
Matrix_MultVec3f(&D_809FF40C, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (limbIndex == 17) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
Matrix_RotateZYX(13062, -1329, -15499, MTXMODE_APPLY);
Matrix_Translate(945.946f, -297.297f, 608.108f, MTXMODE_APPLY);
Matrix_Scale(1.217f, 1.217f, 1.217f, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL);
Matrix_Pop();
CLOSE_DISPS(play->state.gfxCtx);
}
}
}
void EnDu_Draw(Actor* thisx, PlayState* play) {
@@ -1501,7 +1501,7 @@ s32 EnElf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p
s32 EnElf_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnElf* this = (EnElf*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 2) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
+1 -1
View File
@@ -296,7 +296,7 @@ void EnFu_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&sMtxSrc, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 14) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -428,7 +428,7 @@ s32 EnHeishi_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f
s32 EnHeishi4_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnHeishi4* this = (EnHeishi4*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 16) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -170,7 +170,7 @@ void EnHs2_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&D_80A6F4CC, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 9) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
+1 -1
View File
@@ -1206,7 +1206,7 @@ void EnHy_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&sp3C, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
Matrix_Push();
switch (this->actor.params) {
+3 -3
View File
@@ -955,7 +955,7 @@ void EnIk_PostLimbDraw3(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
break;
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 11) {
Matrix_Push();
Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY);
@@ -1249,7 +1249,7 @@ void EnIk_PostLimbDraw2(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
} break;
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 11) {
Matrix_Push();
Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY);
@@ -1408,7 +1408,7 @@ void EnIk_PostLimbDraw1(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
break;
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 11) {
Matrix_Push();
Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY);
+1 -1
View File
@@ -993,7 +993,7 @@ void EnIn_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
gSPDisplayList(POLY_OPA_DISP++, gIngoChildEraPitchForkDL);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 16) {
Matrix_Push();
Matrix_RotateZYX(-8192, -222, -11513, MTXMODE_APPLY);
@@ -8,6 +8,7 @@
#include "overlays/effects/ovl_Effect_Ss_Kakera/z_eff_ss_kakera.h"
#include "objects/gameplay_field_keep/gameplay_field_keep.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "vt.h"
@@ -251,7 +252,7 @@ void EnIshi_SpawnDustLarge(EnIshi* this, PlayState* play) {
void EnIshi_DropCollectible(EnIshi* this, PlayState* play) {
s16 dropParams;
if ((this->actor.params & 1) == ROCK_SMALL) {
if (GameInteractor_Should(VB_ROCK_DROP_ITEM, (this->actor.params & 1) == ROCK_SMALL, this)) {
dropParams = (this->actor.params >> 8) & 0xF;
if (dropParams >= 0xD) {
+1 -1
View File
@@ -313,7 +313,7 @@ void EnJj_Update(Actor* thisx, PlayState* play) {
s32 EnJj_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnJj* this = (EnJj*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 13) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -469,7 +469,7 @@ void EnMa1_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot,
Matrix_MultVec3f(&vec, &this->actor.focus.pos);
}
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -1137,7 +1137,7 @@ s32 EnNiw_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p
s32 EnNiw_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnNiw* this = (EnNiw*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -251,7 +251,7 @@ static Vec3f sConstVec3f = { 0.2f, 0.2f, 0.2f };
s32 EnNiwGirl_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnNiwGirl* this = (EnNiwGirl*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 4) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -590,7 +590,7 @@ s32 EnNiwLady_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3
s32 EnNiwLady_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnNiwLady* this = (EnNiwLady*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 15) {
OPEN_DISPS(play->state.gfxCtx);
Matrix_Push();
@@ -48,7 +48,7 @@ static ColliderCylinderInit sCylinderInit = {
},
{
ELEMTYPE_UNK0,
{ 0xFFCFFFFF, 0x02, 0x08 },
{ 0xFFCFFFFF, 0x00, 0x08 },
{ 0xFFCFFFFF, 0x00, 0x00 },
TOUCH_ON | TOUCH_SFX_WOOD,
BUMP_ON,
@@ -73,12 +73,10 @@ void EnNutsball_Init(Actor* thisx, PlayState* play) {
EnNutsball* this = (EnNutsball*)thisx;
s32 pad;
this->collider.info.toucher.effect = 2;
ActorShape_Init(&this->actor.shape, 400.0f, ActorShadow_DrawCircle, 13.0f);
Collider_InitCylinder(play, &this->collider);
Collider_SetCylinder(play, &this->collider, &this->actor, &sCylinderInit);
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || CVarGetInteger("gLetItSnow", 0)) {
if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
this->objBankIndex = 0;
} else {
this->objBankIndex = Object_GetIndex(&play->objectCtx, sObjectIDs[this->actor.params]);
@@ -2429,7 +2429,7 @@ void EnOssan_DrawStickDirectionPrompts(PlayState* play, EnOssan* this) {
s32 EnOssan_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
EnOssan* this = (EnOssan*)thisx;
if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (CVarGetInteger("gHoliday.Visual.Hats", 0)) {
if (limbIndex == 8) {
switch (this->actor.params) {
case 4: {
@@ -2529,7 +2529,7 @@ s32 EnOssan_OverrideLimbDrawKokiriShopkeeper(PlayState* play, s32 limbIndex, Gfx
gSPSegment(POLY_OPA_DISP++, 0x0A, SEGMENTED_TO_VIRTUAL(sKokiriShopkeeperEyeTextures[this->eyeTextureIdx]));
}
if (limbIndex == 15 && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) {
if (limbIndex == 15 && CVarGetInteger("gHoliday.Visual.Hats", 0)) {
Matrix_Push();
Matrix_RotateZYX(14169, -2215, 0, MTXMODE_APPLY);
Matrix_Translate(1810.811f, -351.351f, -94.595f, MTXMODE_APPLY);

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