mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-06-07 20:21:19 -04:00
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:
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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") });
|
||||
|
||||
@@ -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(); })
|
||||
|
||||
@@ -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,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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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" });
|
||||
@@ -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
|
||||
@@ -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" });
|
||||
@@ -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); }, {});
|
||||
@@ -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" });
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
+102
-50
@@ -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;
|
||||
});
|
||||
}
|
||||
+10
-3
@@ -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>(¤tEvent)) {
|
||||
// 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 +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>();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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...");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user