diff options
| author | Caladius <Caladius@users.noreply.github.com> | 2025-10-30 11:49:39 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-30 11:49:39 -0400 |
| commit | c91588d8af126f68f032b65e552f6d8a029ded22 (patch) | |
| tree | 2f86291b5a742841967a710cd4dd130117f3a5bb /mm/2s2h/Rando/MiscBehavior | |
| parent | 84f3aedf609b2795f3ba39612312a948be97fb8b (diff) | |
[Rando] Implement Traps (#1287)
* Initial Trap Implementation with 1 Trap Example.
* New TrapTypes and Per Trap Custom Messages
* Spruce up Menu and add placeholders for toggling traps
* Reordering and clean up
* Seeded Trap Models
* Trap Type Opt-in
* Add Jinx Trap
* Add Enemy (Like Like) Trap
* Fix Shop Item draws and convert for Progressive
* Placeholder for Custom Model
* Implemented time trap[Please Squash this] (#13)
* Implemented time trap
* Finished comment because im braindead today
* Forgot to fix indentation(whoops)
* Changed things not mess with the games code, added some more Time Trap messages
* Removed a include I forgot to yoink from the base code.
* Removed header define for VerifyTimeSkip
* Clangit
* Missed some leftovers from a merge conflict.
* Clang format throwing a fit over a empty line
* Added Timeskip CVar placeholder
* Fixed issue where game crashes past midnight
* Added Moonfall skipping prevention messures.
* Added fallback loop to handle the remaining cycles in the event of a overflow.
* Cleaner implementation
* Keep it simple stupid
* changed function name to be more accurate
* Removed outdated comment, used TimeSkipInc for moonfall skip prevention
* Reverted max trap(accidentally pushed it while testing)
* Removed old unused value
* Reduced the iteration by 1 to be closer to where it was at before.
* Will this fix Windows?
* Added TRAP_TIME to trapToCvarMap
* Corrected typo on comment
* Clangit...again
* Added calls to load environment values and adjust the weather.
* Update Time Trap and clean up code
* Add Flash effect to Time Trap
* Added a few more timeTrapMessages
* I hate you clang format
* Fix Trading Post Kick Out Time
* Added map for all locations I can think of that close.
* Clangit yet again
* Removed Inn handling because the edge case related to it closing cannot happen
* Corrected a whoopie I made when I was dealing with a conflict
* Removed pointless condition check that is always true
* Removed duped variable
* Removed unnecessary data from SCENE_8ITEMSHO kickout entry
* Removed now used TRADING_POS_ACTOR_PARAM
---------
Co-authored-by: Caladius <clatini88@gmail.com>
* Comment out Kick Out code for later TODO
* Clean out commented code
* Adds Wallet Trap and updates some tooltips
* More Trap Messages
* Even more trap messages
* moooooore messages
* update for clang
* Feedback changes for Time Trap
* Update Trap functions
* Fix Matrix call
* Trap Model Import (Thanks Zrc)
* rename
* Adjust size
* update name for uniformity
* Update mm/2s2h/Rando/MiscBehavior/Traps.cpp
Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com>
* Update mm/2s2h/Rando/MiscBehavior/Traps.cpp
Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com>
* feedback updates
* Update notifications to remove color codes from trap messages when item skip is on
* VBify Object Dependency
* Fix comma
* fix headers
* Refactor ObjectDependency hook
* Check if item get cutscenes are being skipped
* Use brackets for LUS include
* Header update
---------
Co-authored-by: sitton76 <58642183+sitton76@users.noreply.github.com>
Co-authored-by: Eblo <7004497+Eblo@users.noreply.github.com>
Diffstat (limited to 'mm/2s2h/Rando/MiscBehavior')
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp | 21 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/MiscBehavior.h | 1 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 12 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/Traps.cpp | 282 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/Traps.h | 10 |
5 files changed, 324 insertions, 2 deletions
diff --git a/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp b/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp index bef7eb663..9f372fe4f 100644 --- a/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp +++ b/mm/2s2h/Rando/MiscBehavior/CheckQueue.cpp @@ -1,13 +1,16 @@ #include "MiscBehavior.h" +#include <libultraship/bridge/consolevariablebridge.h> #include "2s2h/GameInteractor/GameInteractor.h" #include "2s2h/CustomItem/CustomItem.h" #include "2s2h/CustomMessage/CustomMessage.h" #include "2s2h/BenGui/Notification.h" #include "2s2h/Rando/StaticData/StaticData.h" #include "2s2h/ShipUtils.h" +#include "Traps.h" extern "C" { #include "variables.h" +#include <functions.h> extern TexturePtr gItemIcons[131]; extern s16 D_801CFF94[250]; } @@ -58,10 +61,20 @@ void Rando::MiscBehavior::CheckQueue() { randoItemId = RI_TRIFORCE_PIECE_PREVIOUS; } + if (randoItemId == RI_TRAP) { + prefix = ""; + message = GetTrapMessage(); + // We need to remove the Color Codes if the player is skipping Item Get Cutscenes as the + // Notification Emit doesnt support it. + if (CVarGetInteger("gEnhancements.Cutscenes.SkipGetItemCutscenes", 0) >= 2) { + message = CustomMessage::RemoveColorCodes(message); + } + } + CustomMessage::Entry entry = { .textboxType = 2, .icon = Rando::StaticData::GetIconForZMessage(randoItemId), - .msg = prefix + " " + message + "!", + .msg = (prefix == "" ? "" : prefix + " ") + message + (randoItemId == RI_TRAP ? "" : "!"), }; if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) { @@ -88,7 +101,11 @@ void Rando::MiscBehavior::CheckQueue() { // If the item has been given, the CUSTOM_ITEM_PARAM is set to the RI, prior to that it's the RC if (CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION) { - randoItemId = (RandoItemId)CUSTOM_ITEM_PARAM; + if ((RandoItemId)CUSTOM_ITEM_PARAM == RI_TRAP) { + randoItemId = RI_MAX_TRAP; + } else { + randoItemId = (RandoItemId)CUSTOM_ITEM_PARAM; + } } else { auto& randoSaveCheck = RANDO_SAVE_CHECKS[CUSTOM_ITEM_PARAM]; randoItemId = diff --git a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h index 18432c372..d0f93f436 100644 --- a/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h +++ b/mm/2s2h/Rando/MiscBehavior/MiscBehavior.h @@ -21,6 +21,7 @@ void OnFileCreate(s16 fileNum); void OnFlagSet(FlagType flagType, u32 flag); void OnSceneFlagSet(s16 sceneId, FlagType flagType, u32 flag); void OnSceneInit(s16 sceneId, s8 spawnNum); +void OfferTrapItem(); } // namespace MiscBehavior diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index f41e47afc..6edec7fb1 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -323,6 +323,18 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { throw std::runtime_error("No items in logic"); } + // Handle Shuffling Traps + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRAPS] == RO_GENERIC_YES) { + for (int i = 0; i < RANDO_SAVE_OPTIONS[RO_TRAP_AMOUNT]; i++) { + for (int j = 0; j < itemPool.size(); j++) { + if (itemPool[j] == RI_JUNK) { + itemPool[j] = RI_TRAP; + break; + } + } + } + } + int heartPiecesRemoved = 0; // Add/Remove junk items to/from the pool to make the item pool size match the check pool size while (checkPool.size() != itemPool.size()) { diff --git a/mm/2s2h/Rando/MiscBehavior/Traps.cpp b/mm/2s2h/Rando/MiscBehavior/Traps.cpp new file mode 100644 index 000000000..6a7528dc2 --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/Traps.cpp @@ -0,0 +1,282 @@ +#include "Traps.h" +#include <libultraship/bridge/consolevariablebridge.h> +#include "MiscBehavior.h" +#include "Rando/ActorBehavior/ActorBehavior.h" +#include "2s2h/DeveloperTools/SaveEditor.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "overlays/actors/ovl_En_Time_Tag/z_en_time_tag.h" +void func_80833B18(PlayState* play, Player* thisx, s32 arg2, f32 speed, f32 velocityY, s16 arg5, + s32 invincibilityTimer); +void EnTimeTag_KickOut_Transition(EnTimeTag* enTimeTag, PlayState* play); +} + +#define MORNING_TIME 0x4000 +#define DAY_LENGTH 0x10000 +// Adjust so that 6 A.M. is 0 and 5:59 A.M. is 0xFFFF +#define ZERO_DAY_START(time) (((u16)(time - MORNING_TIME) % DAY_LENGTH)) + +extern void UpdateGameTime(u16 gameTime); + +int roll = TRAP_FREEZE; +const u16 timeSkipInterval = 4000; + +std::map<TrapTypes, const char*> trapToCvarMap = { + { TRAP_FREEZE, "gRando.Traps.Freeze" }, { TRAP_BLAST, "gRando.Traps.Blast" }, + { TRAP_SHOCK, "gRando.Traps.Shock" }, { TRAP_JINX, "gRando.Traps.Jinx" }, + { TRAP_WALLET, "gRando.Traps.Wallet" }, { TRAP_ENEMY, "gRando.Traps.Enemy" }, + { TRAP_TIME, "gRando.Traps.Time" }, +}; + +std::vector<TrapTypes> getEnabledTrapTypes() { + std::vector<TrapTypes> enabledTrapTypes; + for (auto& trap : trapToCvarMap) { + if (CVarGetInteger(trap.second, 0)) { + enabledTrapTypes.push_back(trap.first); + } + } + if (enabledTrapTypes.size() == 0) { + enabledTrapTypes.push_back(TRAP_FREEZE); + } + return enabledTrapTypes; +}; + +int RollTrapType() { + auto enabledTraps = getEnabledTrapTypes(); + roll = enabledTraps[rand() % enabledTraps.size()]; + return roll; +} + +// clang-format off +std::vector<std::string> defaultTrapMessages = { + "This item is available in the %bRando DLC%w.", + "This is what happens when %gCaladius%w is left unsupervised.", + "Oh no!", + "Uh oh!", + "KEKW", + "We've been trying to reach you about your Horse's %gextended warranty%w.", + "Admit it, you wish this was %gGreg%w.", + "Error 404: Item Not Found", + "Get dunked on!", + "This %rTrap%w is brought to you by today's sponsor...\n%gRaid Shadow Legends%w!", +}; + +std::vector<std::string> freezeTrapMessages = { + "%rOcarina of Time%w called, they want their %bIce Trap%w back.", + "Greetings from %bSnowhead%w! Wish you were here.", + "This item was too %bcool%w for you anyway.", + "Let me see your best %bKing Zora%w impersonation.", + "There's no business like %bSnow%w business!", + "How much does a polar bear weigh? Enough to break the %bice%w.", + "You found the %yTrifo%w... Wait, nevermind...", + "Quick Time Event! Don't die.", +}; + +std::vector<std::string> blastTrapMessages = { + "Coming to you live from the %yThunderdome%w!", + "There was supposed to be an Earth shattering %yKaboom%w!", + "Pardon me while I %yburst%w.", + "This is not the greatest %yblast%w in the world, this is just a tribute.", + "Hey look, this %yitem%w is ticking!", + "This is sure to make %gPamela%w leave the house.", + "You found a %yDodongo Snack%w!", +}; + +std::vector<std::string> shockTrapMessages = { + "We're losing him!\n%gCLEAR%w", + "It's got what %gplants%w need.", + "On todays episode of Grey's Anatomy...", + "I'm giving it all I've got, captain!", + "Now simulating a CD in a Microwave.", + "Hang in there, for %gMeryl%w." +}; + +std::vector<std::string> jinxTrapMessages = { + "Looks like someone's got a case of the %bMondays%w. :( ", + "Your wrist hurts! The doctor says no more swords.", +}; + +std::vector<std::string> walletTrapMessages = { + "Spare some %rchange%w?", + "%rBreaking News%w: the Moon hasn't crashed, but Termina's economy sure has." +}; + +std::vector<std::string> enemyTrapMessages = { + "You made a new friend!", + // Like Like Specific, will need to adjust if new enemies are added. + "Someone likes you! They %rLike Like%w you!", + "You don't need a shield anyway.", + "This item sucks.", +}; + +std::vector<std::string> timeTrapMessages = { + "%yTime%w flashes before your eyes!", + "You have played the %ySun's Song%w!", + "The %rGoddess of Time%w smites you!", + "Spent an hour and a half admiring this %bfake item%w.", + "You found a great place to take a nap!", + "Break time! Not like the world is ending right?", + "I just need you to tell me how to get to the %ytime machine%w.", + "Mweep", +}; + +std::map<TrapTypes, std::vector<std::string>> trapMessageList = { + { TRAP_FREEZE, freezeTrapMessages }, + { TRAP_BLAST, blastTrapMessages }, + { TRAP_SHOCK, shockTrapMessages }, + { TRAP_JINX, jinxTrapMessages }, + { TRAP_WALLET, walletTrapMessages }, + { TRAP_ENEMY, enemyTrapMessages }, + { TRAP_TIME, timeTrapMessages }, +}; +// clang-format on + +std::string GetTrapMessage() { + RollTrapType(); + auto findIt = trapMessageList.find((TrapTypes)roll); + if (findIt == trapMessageList.end()) { + return defaultTrapMessages[rand() % defaultTrapMessages.size()]; + } else { + return findIt->second[rand() % findIt->second.size()]; + } +} + +void Rando::MiscBehavior::OfferTrapItem() { + if (!gPlayState) { + return; + } + + switch (roll) { + case TRAP_FREEZE: + GameInteractor::Instance->events.emplace_back( + GIEventTrap{ .action = []() { func_80833B18(gPlayState, GET_PLAYER(gPlayState), 3, 0, 0, 0, 0); } }); + break; + case TRAP_BLAST: + GameInteractor::Instance->events.emplace_back(GIEventTrap{ .action = []() { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_BOM, GET_PLAYER(gPlayState)->actor.world.pos.x, + GET_PLAYER(gPlayState)->actor.world.pos.y, GET_PLAYER(gPlayState)->actor.world.pos.z, 1, 0, + 0, 0); + } }); + break; + case TRAP_SHOCK: + GameInteractor::Instance->events.emplace_back( + GIEventTrap{ .action = []() { func_80833B18(gPlayState, GET_PLAYER(gPlayState), 4, 0, 0, 0, 0); } }); + break; + case TRAP_JINX: + GameInteractor::Instance->events.emplace_back(GIEventTrap{ .action = []() { + Actor_PlaySfx(&GET_PLAYER(gPlayState)->actor, NA_SE_EN_BUBLE_BITE); + gSaveContext.jinxTimer = 1200; + } }); + break; + case TRAP_WALLET: + GameInteractor::Instance->events.emplace_back(GIEventTrap{ .action = []() { + int16_t currentRupees = gSaveContext.save.saveInfo.playerData.rupees; + if (currentRupees != 0) { + Vec3f positional = GET_PLAYER(gPlayState)->actor.world.pos; + positional.y = GET_PLAYER(gPlayState)->actor.world.pos.y + 100.0f; + Item00Type rupee = ITEM00_RUPEE_GREEN; + int16_t spawnedRupees = 0; + int16_t remainingRupees = currentRupees; + for (int i = spawnedRupees; spawnedRupees < remainingRupees;) { + if (currentRupees >= 20) { + rupee = ITEM00_RUPEE_RED; + spawnedRupees += 20; + Rupees_ChangeBy(-20); + currentRupees -= 20; + } else if (currentRupees >= 5) { + rupee = ITEM00_RUPEE_BLUE; + spawnedRupees += 5; + Rupees_ChangeBy(-5); + currentRupees -= 5; + } else if (currentRupees >= 1) { + rupee = ITEM00_RUPEE_GREEN; + spawnedRupees += 1; + Rupees_ChangeBy(-1); + currentRupees -= 1; + } + EnItem00* rupeeActor = (EnItem00*)Item_DropCollectible(gPlayState, &positional, rupee); + rupeeActor->actor.speed = Rand_CenteredFloat(5.0f); + rupeeActor->unk152 = 600; // Extending Time before Despawning + } + } + } }); + break; + case TRAP_ENEMY: + GameInteractor::Instance->events.emplace_back(GIEventTrap{ .action = []() { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_RR, GET_PLAYER(gPlayState)->actor.world.pos.x, + GET_PLAYER(gPlayState)->actor.world.pos.y, GET_PLAYER(gPlayState)->actor.world.pos.z, 0, 0, + 0, 1); + } }); + break; + case TRAP_TIME: + GameInteractor::Instance->events.emplace_back(GIEventTrap{ .action = []() { + u16 previous_time = gSaveContext.save.time; + u16 new_time = gSaveContext.save.time + timeSkipInterval; + if (previous_time < MORNING_TIME && new_time >= MORNING_TIME) { + // Handles case where Night -> Day + if (gSaveContext.save.day != 3) { + gSaveContext.save.day++; + gSaveContext.save.eventDayCount++; + UpdateGameTime(new_time); + Interface_NewDay(gPlayState, CURRENT_DAY); + // Load environment values for new day + Environment_NewDay(&gPlayState->envCtx); + // Clear weather from day 2 + gWeatherMode = WEATHER_MODE_CLEAR; + gPlayState->envCtx.lightningState = LIGHTNING_OFF; + } else { + // Handles Moonfall case, prevents skipping past it by setting time right before Moonfall. + UpdateGameTime(MORNING_TIME - (timeSkipInterval / 10)); + } + } else { + // Every other case + UpdateGameTime(new_time); + } + TransitionFade_SetColor(&gPlayState->unk_18E48, 0x000000); + R_TRANS_FADE_FLASH_ALPHA_STEP = -1; + Player_PlaySfx(GET_PLAYER(gPlayState), NA_SE_SY_TRANSFORM_MASK_FLASH); + + // Handle kickouts, if needed + EnTimeTag* enTimeTag = (EnTimeTag*)Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, + ACTOR_EN_TIME_TAG, ACTORCAT_ITEMACTION, 99999.9f); + if (enTimeTag != nullptr) { + TimeTagType timeTagType = (TimeTagType)TIMETAG_GET_TYPE(&enTimeTag->actor); + if (timeTagType == TIMETAG_KICKOUT_DOOR || timeTagType >= TIMETAG_KICKOUT_FINAL_HOURS) { + s16 kickoutHour = TIMETAG_KICKOUT_HOUR(&enTimeTag->actor); + s16 kickoutMinute = TIMETAG_KICKOUT_MINUTE(&enTimeTag->actor); + s32 kickoutTime = CLOCK_TIME(kickoutHour, kickoutMinute); + kickoutTime = ZERO_DAY_START(kickoutTime); + previous_time = ZERO_DAY_START(previous_time); + new_time = ZERO_DAY_START(new_time); + // If we were here before the kickout time, and now it's after, then get out of my house + if (previous_time <= kickoutTime && new_time >= kickoutTime) { + // Unless this is the Stock Pot Inn, and the room key is obtained + if (!(gPlayState->sceneId == SCENE_YADOYA && + Flags_GetRandoInf(RANDO_INF_OBTAINED_ROOM_KEY))) { + // This comes from EnTimeTag_KickOut_WaitForTime + Player_SetCsActionWithHaltedActors(gPlayState, &enTimeTag->actor, PLAYER_CSACTION_WAIT); + Message_StartTextbox(gPlayState, 0x1883 + TIMETAG_KICKOUT_GET_TEXT(&enTimeTag->actor), + NULL); + enTimeTag->actionFunc = EnTimeTag_KickOut_Transition; + } + } + } + } + } }); + break; + default: + break; + } +} + +void Rando::ActorBehavior::InitTrapsBehavior() { + // Selectively disable object dependency for actors spawned by traps + COND_VB_SHOULD(VB_ENABLE_OBJECT_DEPENDENCY, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_TRAPS], { + ObjectId objectId = (ObjectId)va_arg(args, int); + if (objectId == OBJECT_RR) { // Like-Like + *should = false; + } + }); +}
\ No newline at end of file diff --git a/mm/2s2h/Rando/MiscBehavior/Traps.h b/mm/2s2h/Rando/MiscBehavior/Traps.h new file mode 100644 index 000000000..a842c2462 --- /dev/null +++ b/mm/2s2h/Rando/MiscBehavior/Traps.h @@ -0,0 +1,10 @@ +#ifndef RANDO_TRAP_H +#define RANDO_TRAP_H + +#include "Rando/Rando.h" +typedef enum { TRAP_FREEZE, TRAP_BLAST, TRAP_SHOCK, TRAP_JINX, TRAP_WALLET, TRAP_ENEMY, TRAP_TIME, TRAP_MAX } TrapTypes; + +extern int RollTrapType(); +extern std::string GetTrapMessage(); + +#endif
\ No newline at end of file |
