Skip to content

Commit

Permalink
Rando: Add Checks for Milk Bar NPC Shop (#217)
Browse files Browse the repository at this point in the history
* Rando: Add Checks for Mr Barten's Milk Bar Shop Prompts (Milk/Chateau)

* Rando: Add custom behavior for Mr Barten to support randomizing his shop options

* Refactor: Alphabetically list Milk Bar npc shop checks

* Refactor: Alphabetically list Milk Bar npc shop checks in Types.h

* Refactor: Clean up references to Milk Bar purchase checks

* Engine: Pass callback and script pointers as VB_EXEC_MSG_EVENT hook parameters

* Add dialogue state tracking to EnTab ActorBehavior

* Override default script and callback behavior in EnTab ActorBehavior

* Clang-format on EnTab and remove extraneous LUSLOGs

* Last pass on clang-format for #217

* Remove unused reference to EnTab actor script that was crashing builds on Linux/Mac

* Add QueueNonCheckItem to MiscBehavior.h/cpp

Allows actors to queue up Give Item events which go through the same flow as the Rando Save check queue. Should only be used for non-key items.

* Swap EnTab behavior to granting RI Refills and remove all vanilla bottle checks

* EnTab clang-format pass

* Update mm/2s2h/Rando/ActorBehavior/EnTab.cpp

Co-authored-by: Garrett Cox <[email protected]>

* Revert "Add QueueNonCheckItem to MiscBehavior.h/cpp"

This reverts commit 4c274db.

* Rando EnTab: Add RO_SHUFFLE_SHOPS check to SHOULD conditionals

* Update EnTab rando shop shuffle behavior to give vanilla refill behavior with delayed bottle check

* Remove now-unused MsgScript** parameter in VB_EXEC_MSG_EVENT

This was introduced in an earlier commit for #217 but is no longer a necessary addition to the engine as a result of 5a4f042.

* Remove unused LUSLOG that was breaking builds on Linux/Mac

---------

Co-authored-by: Garrett Cox <[email protected]>
  • Loading branch information
JoshSanch and garrettjoecox authored Feb 13, 2025
1 parent 9cbfa47 commit 4d25d0d
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 2 deletions.
1 change: 1 addition & 0 deletions mm/2s2h/Rando/ActorBehavior/ActorBehavior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ void Rando::ActorBehavior::OnFileLoad() {
Rando::ActorBehavior::InitEnSshBehavior();
Rando::ActorBehavior::InitEnStoneheishiBehavior();
Rando::ActorBehavior::InitEnSyatekiManBehavior();
Rando::ActorBehavior::InitEnTabBehavior();
Rando::ActorBehavior::InitEnTalkBehavior();
Rando::ActorBehavior::InitEnTotoBehavior();
Rando::ActorBehavior::InitEnTrtBehavior();
Expand Down
1 change: 1 addition & 0 deletions mm/2s2h/Rando/ActorBehavior/ActorBehavior.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ void InitEnSob1Behavior();
void InitEnSshBehavior();
void InitEnStoneheishiBehavior();
void InitEnSyatekiManBehavior();
void InitEnTabBehavior();
void InitEnTalkBehavior();
void InitEnTotoBehavior();
void InitEnTrtBehavior();
Expand Down
185 changes: 185 additions & 0 deletions mm/2s2h/Rando/ActorBehavior/EnTab.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#include "ActorBehavior.h"
#include "Rando/MiscBehavior/MiscBehavior.h"
#include <libultraship/libultraship.h>

extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Tab/z_en_tab.h"
}

#define ENTAB_EMPTY_BOTTLE_CHECK_FAILED_SCRIPT_POS 0x000F
#define MSCRIPT_OFFER_ITEM_CMD_LEN 0x5

static bool isInitialGiveItemMscriptCommandExecution = false;

int32_t EnTab_OverrideBottleCheckCallback(Actor* thisx, PlayState* play) {
// Should always return true - as if the player always has an empty bottle!
// See func_80BE0D38 in z_en_tab.c
return true;
}

void EnTab_OnOpenShopText(u16* textId, bool* loadFromMessageTable) {
RandoSaveCheck milkPurchaseCheck = RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_MILK];
RandoSaveCheck chateauPurchaseCheck = RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_CHATEAU];

RandoItemId riMilkPurchase = Rando::ConvertItem(milkPurchaseCheck.randoItemId, RC_MILK_BAR_PURCHASE_MILK);
RandoItemId riChateauPurchase = Rando::ConvertItem(chateauPurchaseCheck.randoItemId, RC_MILK_BAR_PURCHASE_CHATEAU);

auto entry = CustomMessage::LoadVanillaMessageTableEntry(*textId);
entry.autoFormat = false;

entry.msg = "\x02\xC3{item1}\x01 {price1} Rupees\x11"
"\x02{item2}\x01 {price2} Rupees\x11"
"\x02Nothing";

std::string itemName1 = "Milk";
std::string itemPrice1 = "20";
if (!milkPurchaseCheck.cycleObtained) {
itemName1 = Rando::StaticData::Items[riMilkPurchase].name;
itemPrice1 = std::to_string(milkPurchaseCheck.price);
}

std::string itemName2 = "Chateau Romani";
std::string itemPrice2 = "200";
if (!chateauPurchaseCheck.cycleObtained) {
itemName2 = Rando::StaticData::Items[riChateauPurchase].name;
itemPrice2 = std::to_string(chateauPurchaseCheck.price);
}

CustomMessage::Replace(&entry.msg, "{item1}", itemName1);
CustomMessage::Replace(&entry.msg, "{item2}", itemName2);
CustomMessage::Replace(&entry.msg, "{price1}", itemPrice1);
CustomMessage::Replace(&entry.msg, "{price2}", itemPrice2);
CustomMessage::EnsureMessageEnd(&entry.msg);
CustomMessage::LoadCustomMessageIntoFont(entry);
*loadFromMessageTable = false;
};

void Rando::ActorBehavior::InitEnTabBehavior() {
// Give the randomized items instead if they haven't already been purchased
// Otherwise, give vanilla refills but check for bottles first
COND_VB_SHOULD(VB_GIVE_ITEM_FROM_OFFER, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS], {
GetItemId* item = va_arg(args, GetItemId*);
Actor* actor = va_arg(args, Actor*);

if (actor->id == ACTOR_EN_TAB) {
RandoCheckId checkId =
gPlayState->msgCtx.choiceIndex == 0 ? RC_MILK_BAR_PURCHASE_MILK : RC_MILK_BAR_PURCHASE_CHATEAU;

if (!RANDO_SAVE_CHECKS[checkId].cycleObtained) {
RANDO_SAVE_CHECKS[checkId].eligible = true;
*should = false;
} else if (!Inventory_HasEmptyBottle()) {
*should = false;
}

// Update actor parent to avoid looping over MSCRIPT_OFFER_ITEM infinitely
Player* player = GET_PLAYER(gPlayState);
EnTab* tabActor = (EnTab*)actor;
tabActor->actor.parent = &player->actor;
}
});

// Use the randomized prices for message script branching/game state updates
COND_VB_SHOULD(VB_EXEC_MSG_EVENT, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS], {
u32 cmdId = va_arg(args, u32);
Actor* actor = va_arg(args, Actor*);
MsgScript* script = va_arg(args, MsgScript*);
MsgEventCallback* callback = va_arg(args, MsgEventCallback*);

// Override actor parent to skip item grant if the player is trying to buy
// vanilla milk items and does not have a bottle
if (cmdId == MSCRIPT_CMD_06) {
RandoCheckId checkId =
gPlayState->msgCtx.choiceIndex == 0 ? RC_MILK_BAR_PURCHASE_MILK : RC_MILK_BAR_PURCHASE_CHATEAU;

if (RANDO_SAVE_CHECKS[checkId].cycleObtained && isInitialGiveItemMscriptCommandExecution &&
!Inventory_HasEmptyBottle()) {
// Make sure that skip branch is taken in Mscript handler by setting actor parent to player
Player* player = GET_PLAYER(gPlayState);
EnTab* tabActor = (EnTab*)actor;
tabActor->actor.parent = &player->actor;

// Update skip offset to point to Open Bottle Failure MsgScript data
s16 skipOffset = 0;
if (gPlayState->msgCtx.choiceIndex == 0) {
skipOffset = ENTAB_EMPTY_BOTTLE_CHECK_FAILED_SCRIPT_POS - 0x004F - MSCRIPT_OFFER_ITEM_CMD_LEN;
} else {
skipOffset = ENTAB_EMPTY_BOTTLE_CHECK_FAILED_SCRIPT_POS - 0x0040 - MSCRIPT_OFFER_ITEM_CMD_LEN;
}

script[3] = skipOffset >> 8; // upper byte of skipOffset
script[4] = skipOffset & 0xFF; // lower byte of skipOffset

// Add helpful error sound to alert player of custom bottle check behavior
Audio_PlaySfx(NA_SE_SY_ERROR);
} else {
// Write vanilla values to skip offset for script command
s16 skipOffset = 0;
script[3] = skipOffset >> 8;
script[4] = skipOffset & 0xFF;
}

if (isInitialGiveItemMscriptCommandExecution) {
isInitialGiveItemMscriptCommandExecution = false;
}
}

// Use check prices instead of vanilla for MSCRIPT_BRANCH_ON_RUPEES
if (cmdId == MSCRIPT_CMD_08) {
s16 checkPrice = 0;
if (gPlayState->msgCtx.choiceIndex == 0) {
checkPrice = 20;

if (!RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_MILK].cycleObtained) {
checkPrice = RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_MILK].price;
}
} else {
checkPrice = 200;

if (!RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_CHATEAU].cycleObtained) {
checkPrice = RANDO_SAVE_CHECKS[RC_MILK_BAR_PURCHASE_CHATEAU].price;
}
}

script[1] = checkPrice >> 8; // upper byte of price
script[2] = checkPrice & 0xFF; // lower byte of price
}

// MSCRIPT_BEGIN_TEXT
if (cmdId == MSCRIPT_CMD_14) {
isInitialGiveItemMscriptCommandExecution = true;
}

// Need to reset actor parent which otherwise would've been reset by vanilla Give Item cutscene in MSCRIPT_DONE
if (cmdId == MSCRIPT_CMD_16) {
Player* player = GET_PLAYER(gPlayState);
EnTab* tabActor = (EnTab*)actor;
tabActor->actor.parent = NULL;
}

// Charge Link the randomized price when calling MSCRIPT_CHANGE_RUPEES
// Will not charge Link when purchasing vanilla refills without an empty bottle
if (cmdId == MSCRIPT_CMD_20) {
RandoCheckId checkId =
gPlayState->msgCtx.choiceIndex == 0 ? RC_MILK_BAR_PURCHASE_MILK : RC_MILK_BAR_PURCHASE_CHATEAU;
s16 rupeeChangeAmt = gPlayState->msgCtx.choiceIndex == 0 ? -20 : -200;

if (!RANDO_SAVE_CHECKS[checkId].cycleObtained) {
rupeeChangeAmt = -RANDO_SAVE_CHECKS[checkId].price;
} else if (!Inventory_HasEmptyBottle()) {
rupeeChangeAmt = 0;
}

script[1] = rupeeChangeAmt >> 8; // upper byte of price
script[2] = rupeeChangeAmt & 0xFF; // lower byte of price
}

// Override callback function depending on actor state for MSCRIPT_BRANCH_ON_CALLBACK_2
if (cmdId == MSCRIPT_CMD_40) {
*callback = EnTab_OverrideBottleCheckCallback;
}
});

COND_ID_HOOK(OnOpenText, 0x2B0B, IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS], EnTab_OnOpenShopText);
}
3 changes: 2 additions & 1 deletion mm/2s2h/Rando/Logic/Regions/Central.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,10 @@ static RegisterShipInitFunc initFunc([]() {
};
Regions[RR_MILK_BAR] = RandoRegion{ .sceneId = SCENE_MILK_BAR,
.checks = {
// TODO : Add shop checks.
CHECK(RC_MILK_BAR_CIRCUS_LEADER_MASK, CAN_BE_DEKU && CAN_BE_GORON && CAN_BE_ZORA && HAS_ITEM(ITEM_OCARINA_OF_TIME)),
CHECK(RC_MILK_BAR_MADAME_AROMA, HAS_ITEM(ITEM_MASK_KAFEIS_MASK) && Flags_GetRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA)),
CHECK(RC_MILK_BAR_PURCHASE_CHATEAU, CAN_AFFORD(RC_MILK_BAR_PURCHASE_CHATEAU) && HAS_ITEM(ITEM_MASK_ROMANI)),
CHECK(RC_MILK_BAR_PURCHASE_MILK, CAN_AFFORD(RC_MILK_BAR_PURCHASE_MILK) && HAS_ITEM(ITEM_MASK_ROMANI)),
},
.exits = { // TO FROM
EXIT(ENTRANCE(EAST_CLOCK_TOWN, 11), ENTRANCE(MILK_BAR, 0), true),
Expand Down
2 changes: 2 additions & 0 deletions mm/2s2h/Rando/StaticData/Checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ std::map<RandoCheckId, RandoStaticCheck> Checks = {
RC(RC_MAYORS_OFFICE_PIECE_OF_HEART, RCTYPE_NPC, SCENE_SONCHONOIE, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_MAYOR_HP, RI_HEART_PIECE),
RC(RC_MILK_BAR_CIRCUS_LEADER_MASK, RCTYPE_NPC, SCENE_MILK_BAR, FLAG_NONE, 0x00, RI_MASK_CIRCUS_LEADER),
RC(RC_MILK_BAR_MADAME_AROMA, RCTYPE_NPC, SCENE_MILK_BAR, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_57_04, RI_BOTTLE_CHATEAU_ROMANI),
RC(RC_MILK_BAR_PURCHASE_CHATEAU, RCTYPE_SHOP, SCENE_MILK_BAR, FLAG_NONE, 0x00, RI_CHATEAU_ROMANI_REFILL),
RC(RC_MILK_BAR_PURCHASE_MILK, RCTYPE_SHOP, SCENE_MILK_BAR, FLAG_NONE, 0x00, RI_MILK_REFILL),
RC(RC_MILK_ROAD_OWL_STATUE, RCTYPE_OWL, SCENE_ROMANYMAE, FLAG_NONE, 0x00, RI_OWL_MILK_ROAD),
RC(RC_MILK_ROAD_TINGLE_MAP_01, RCTYPE_TINGLE_SHOP, SCENE_ROMANYMAE, FLAG_NONE, 0x00, RI_TINGLE_MAP_ROMANI_RANCH),
RC(RC_MILK_ROAD_TINGLE_MAP_02, RCTYPE_TINGLE_SHOP, SCENE_ROMANYMAE, FLAG_NONE, 0x00, RI_TINGLE_MAP_GREAT_BAY),
Expand Down
2 changes: 2 additions & 0 deletions mm/2s2h/Rando/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ typedef enum {
RC_MAYORS_OFFICE_PIECE_OF_HEART,
RC_MILK_BAR_CIRCUS_LEADER_MASK,
RC_MILK_BAR_MADAME_AROMA,
RC_MILK_BAR_PURCHASE_CHATEAU,
RC_MILK_BAR_PURCHASE_MILK,
RC_MILK_ROAD_OWL_STATUE,
RC_MILK_ROAD_TINGLE_MAP_01,
RC_MILK_ROAD_TINGLE_MAP_02,
Expand Down
2 changes: 1 addition & 1 deletion mm/src/code/z_msgevent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ s32 MsgEvent_RunScript(Actor* actor, PlayState* play, MsgScript* script, MsgEven
}

// Run command handler
} while (!GameInteractor_Should(VB_EXEC_MSG_EVENT, true, cmdId, actor, script) ||
} while (!GameInteractor_Should(VB_EXEC_MSG_EVENT, true, cmdId, actor, script, &callback) ||
sMsgEventCmdHandlers[cmdId](actor, play, &script, callback, &scriptDone) == MSCRIPT_CONTINUE);

cur = script;
Expand Down

0 comments on commit 4d25d0d

Please sign in to comment.