Files
Shiip-of-Hakinian-Espanol/soh/soh/Enhancements/randomizer/item.cpp
Christopher Leggett 7b3efb1e7b Upgrades Custom Messages to Hooks and ShipInit (#5101)
Defines the hook for OnTextLoad. Intercepts message loading with hooks. Adds file to handle the CustomMessage creation for items.

Handles Ice Traps, Triforce Pieces, and Custom Items. Handle maps, compasses, and keys

Converts gossip stone hints to hook. Handle one-off merchant messages. Convert scrubs and shop text and remove now-unused code

Convert Sheik and Ganondorf hint text. Convert skulltula people messages. Convert more static hints

Specifically Dampe, Greg, Warp Songs, Frogs, Loach, Fishing Pole, and Saria

Convert Biggoron Hint. Convert Big Poes hint. Convert Anju hint dialogue. Convert Malon hint. Convert Horseback Archery hints. Convert Mask Shop SIgn hint

Convert Lake Hylia Switch related text. Convert Shooting Gallery bow reminder message

Convert random rupee names. Convert Rando-Relevant Navi Enhancement. Convert Random Goron messages

Convert Injecting Skull token counts. Add in a way to AutoFormat with an item icon.. Fix some errors with skull tokens and apply icon

Convert heart container item counts. Convert Inject Item Count for Heart Pieces. Port Better Bombchu Shopping. Convert Market Sneak

Port Quit Fishing At Door and clean up unused stuff. Reintroduce missing Mysterious warp song hint

Make ShipInitFuncs static

Adds and uses per-item articles for get item messages

Fix Entrance Hints and port toggle from dev-copper
2026-01-15 20:57:13 +00:00

496 lines
17 KiB
C++

#include "item.h"
#include "item_location.h"
#include "SeedContext.h"
#include "logic.h"
#include "3drando/item_pool.hpp"
#include "z64item.h"
#include "variables.h"
#include "macros.h"
#include "functions.h"
#include "../../OTRGlobals.h"
namespace Rando {
Item::Item()
: randomizerGet(RG_NONE), type(ITEMTYPE_ITEM), getItemId(GI_NONE), advancement(false), hintKey(RHT_NONE),
progressive(false), price(0) {
}
Item::Item(const RandomizerGet randomizerGet_, Text name_, const ItemType type_, const int16_t getItemId_,
const bool advancement_, LogicVal logicVal_, const RandomizerHintTextKey hintKey_, const uint16_t itemId_,
const uint16_t objectId_, const uint16_t gid_, const uint16_t textId_, const uint16_t field_,
const int16_t chestAnimation_, const GetItemCategory category_, const uint16_t modIndex_, Text article_,
const std::string color_, const bool progressive_, const uint16_t price_)
: randomizerGet(randomizerGet_), name(std::move(name_)), type(type_), getItemId(getItemId_),
advancement(advancement_), logicVal(logicVal_), hintKey(hintKey_), category(category_),
article(std::move(article_)), color(std::move(color_)), progressive(progressive_), price(price_) {
if (modIndex_ == MOD_RANDOMIZER || getItemId > 0x7D) {
giEntry = std::make_shared<GetItemEntry>(GetItemEntry{
itemId_, field_, static_cast<int16_t>((chestAnimation_ != CHEST_ANIM_SHORT ? 1 : -1) * (gid_ + 1)), textId_,
objectId_, modIndex_, TABLE_RANDOMIZER, static_cast<int16_t>(randomizerGet_), gid_, true, ITEM_FROM_NPC,
category_, static_cast<uint16_t>(randomizerGet_), MOD_RANDOMIZER, NULL });
} else {
giEntry = std::make_shared<GetItemEntry>(GetItemEntry{
itemId_, field_, static_cast<int16_t>((chestAnimation_ != CHEST_ANIM_SHORT ? 1 : -1) * (gid_ + 1)), textId_,
objectId_, modIndex_, TABLE_VANILLA, getItemId_, gid_, true, ITEM_FROM_NPC, category_, itemId_, modIndex_,
NULL });
}
}
Item::Item(const RandomizerGet randomizerGet_, Text name_, const ItemType type_, const int16_t getItemId_,
const bool advancement_, LogicVal logicVal_, const RandomizerHintTextKey hintKey_,
const GetItemCategory category_, Text article_, const std::string color_, const bool progressive_,
const uint16_t price_)
: randomizerGet(randomizerGet_), name(std::move(name_)), type(type_), getItemId(getItemId_),
advancement(advancement_), logicVal(logicVal_), hintKey(hintKey_), category(category_),
article(std::move(article_)), color(std::move(color_)), progressive(progressive_), price(price_) {
}
void Item::ApplyEffect() const {
auto ctx = Rando::Context::GetInstance();
auto logic = ctx->GetLogic();
if (!logic->CalculatingAvailableChecks) {
logic->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), true);
}
logic->Set(logicVal, true);
}
void Item::UndoEffect() const {
auto ctx = Rando::Context::GetInstance();
auto logic = ctx->GetLogic();
if (!logic->CalculatingAvailableChecks) {
logic->ApplyItemEffect(StaticData::RetrieveItem(randomizerGet), false);
}
logic->Set(logicVal, false);
}
const Text& Item::GetName() const {
return name;
}
const Text& Item::GetArticle() const {
return article;
}
const std::string& Item::GetColor() const {
return color;
}
bool Item::IsAdvancement() const {
return advancement;
}
int Item::GetItemID() const {
return getItemId;
}
ItemType Item::GetItemType() const {
return type;
}
LogicVal Item::GetLogicVal() const {
return logicVal;
}
RandomizerGet Item::GetRandomizerGet() const {
return randomizerGet;
}
uint16_t Item::GetPrice() const {
return price;
}
std::shared_ptr<GetItemEntry> Item::GetGIEntry() const { // NOLINT(*-no-recursion)
if (giEntry != nullptr && giEntry->itemId != RG_PROGRESSIVE_BOMBCHU_BAG) {
return giEntry;
}
std::shared_ptr<Rando::Context> ctx = Rando::Context::GetInstance();
auto logic = ctx->GetLogic();
RandomizerGet actual = RG_NONE;
const bool tycoonWallet = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_INCLUDE_TYCOON_WALLET);
const u8 infiniteUpgrades = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_INFINITE_UPGRADES);
switch (randomizerGet) {
case RG_PROGRESSIVE_STICK_UPGRADE:
switch (logic->CurrentUpgrade(UPG_STICKS)) {
case 0:
if (ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG)) {
actual = RG_DEKU_STICK_BAG;
break;
}
[[fallthrough]];
case 1:
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_STICK_UPGRADE_INF;
} else {
actual = RG_DEKU_STICK_CAPACITY_20;
}
break;
case 2:
actual = RG_DEKU_STICK_CAPACITY_30;
break;
case 3:
case 4:
if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
actual = RG_STICK_UPGRADE_INF;
} else {
actual = RG_DEKU_STICK_CAPACITY_30;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_NUT_UPGRADE:
switch (logic->CurrentUpgrade(UPG_NUTS)) {
case 0:
if (ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG)) {
actual = RG_DEKU_NUT_BAG;
break;
}
[[fallthrough]];
case 1:
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_NUT_UPGRADE_INF;
} else {
actual = RG_DEKU_NUT_CAPACITY_30;
}
break;
case 2:
actual = RG_DEKU_NUT_CAPACITY_40;
break;
case 3:
case 4:
if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
actual = RG_NUT_UPGRADE_INF;
} else {
actual = RG_DEKU_NUT_CAPACITY_40;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_BOMB_BAG:
switch (logic->CurrentUpgrade(UPG_BOMB_BAG)) {
case 0:
actual = RG_BOMB_BAG;
break;
case 1:
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_BOMB_BAG_INF;
} else {
actual = RG_BIG_BOMB_BAG;
}
break;
case 2:
actual = RG_BIGGEST_BOMB_BAG;
break;
case 3:
case 4:
if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
actual = RG_BOMB_BAG_INF;
} else {
actual = RG_BIGGEST_BOMB_BAG;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_BOW:
switch (logic->CurrentUpgrade(UPG_QUIVER)) {
case 0:
actual = RG_FAIRY_BOW;
break;
case 1:
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_QUIVER_INF;
} else {
actual = RG_BIG_QUIVER;
}
break;
case 2:
actual = RG_BIGGEST_QUIVER;
break;
case 3:
case 4:
if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
actual = RG_QUIVER_INF;
} else {
actual = RG_BIGGEST_QUIVER;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_SLINGSHOT:
switch (logic->CurrentUpgrade(UPG_BULLET_BAG)) {
case 0:
actual = RG_FAIRY_SLINGSHOT;
break;
case 1:
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_BULLET_BAG_INF;
} else {
actual = RG_BIG_BULLET_BAG;
}
break;
case 2:
actual = RG_BIGGEST_BULLET_BAG;
break;
case 3:
case 4:
if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
actual = RG_BULLET_BAG_INF;
} else {
actual = RG_BIGGEST_BULLET_BAG;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_OCARINA:
switch (logic->CurrentInventory(ITEM_OCARINA_FAIRY)) {
case ITEM_NONE:
actual = RG_FAIRY_OCARINA;
break;
case ITEM_OCARINA_FAIRY:
case ITEM_OCARINA_TIME:
actual = RG_OCARINA_OF_TIME;
break;
default:
break;
}
break;
case RG_PROGRESSIVE_HOOKSHOT:
switch (logic->CurrentInventory(ITEM_HOOKSHOT)) {
case ITEM_NONE:
actual = RG_HOOKSHOT;
break;
case ITEM_HOOKSHOT:
case ITEM_LONGSHOT:
actual = RG_LONGSHOT;
break;
default:
break;
}
break;
case RG_PROGRESSIVE_STRENGTH:
switch (logic->CurrentUpgrade(UPG_STRENGTH)) {
case 0:
actual = RG_GORONS_BRACELET;
break;
case 1:
actual = RG_SILVER_GAUNTLETS;
break;
case 2:
case 3:
actual = RG_GOLDEN_GAUNTLETS;
break;
default:
break;
}
break;
case RG_PROGRESSIVE_WALLET:
if (!logic->CheckRandoInf(RAND_INF_HAS_WALLET)) {
actual = RG_CHILD_WALLET;
break;
}
switch (logic->CurrentUpgrade(UPG_WALLET)) {
case 0:
actual = RG_ADULT_WALLET;
break;
case 1:
actual = RG_GIANT_WALLET;
break;
case 2:
if (tycoonWallet) {
actual = RG_TYCOON_WALLET;
break;
}
// fallthrough
case 3:
case 4:
if (infiniteUpgrades != RO_INF_UPGRADES_OFF) {
actual = RG_WALLET_INF;
} else {
actual = tycoonWallet ? RG_TYCOON_WALLET : RG_GIANT_WALLET;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_SCALE:
if (!logic->CheckRandoInf(RAND_INF_CAN_SWIM)) {
actual = RG_BRONZE_SCALE;
break;
}
switch (logic->CurrentUpgrade(UPG_SCALE)) {
case 0:
actual = RG_SILVER_SCALE;
break;
case 1:
case 2:
actual = RG_GOLDEN_SCALE;
break;
default:
break;
}
break;
case RG_PROGRESSIVE_MAGIC_METER:
switch (logic->GetSaveContext()->magicLevel) {
case 0:
actual = RG_MAGIC_SINGLE;
break;
case 1:
actual = RG_MAGIC_DOUBLE;
break;
case 2:
case 3:
if (infiniteUpgrades != RO_INF_UPGRADES_OFF) {
actual = RG_MAGIC_INF;
} else {
actual = RG_MAGIC_DOUBLE;
}
break;
default:
break;
}
break;
case RG_PROGRESSIVE_GORONSWORD: // todo progressive?
actual = RG_BIGGORON_SWORD;
break;
case RG_PROGRESSIVE_BOMBCHU_BAG:
if (OTRGlobals::Instance->gRandoContext->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE)) {
if (logic->CurrentInventory(ITEM_BOMBCHU) != ITEM_NONE) {
if (infiniteUpgrades != RO_INF_UPGRADES_OFF) {
actual = RG_BOMBCHU_INF;
} else {
actual = RG_BOMBCHU_10;
}
}
} else if (OTRGlobals::Instance->gRandoContext->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) {
if (logic->CurrentInventory(ITEM_BOMBCHU) != ITEM_NONE) {
if (infiniteUpgrades == RO_INF_UPGRADES_CONDENSED_PROGRESSIVE) {
actual = RG_BOMBCHU_INF;
} else if (infiniteUpgrades == RO_INF_UPGRADES_PROGRESSIVE) {
if (logic->GetSaveContext()->ship.quest.data.randomizer.bombchuUpgradeLevel >= 3) {
actual = RG_BOMBCHU_INF;
}
}
}
}
break;
default:
actual = RG_NONE;
break;
}
if (giEntry != nullptr && actual == RG_NONE) {
return giEntry;
}
return StaticData::RetrieveItem(actual).GetGIEntry();
}
GetItemEntry Item::GetGIEntry_Copy() const {
return *GetGIEntry();
}
void Item::SetPrice(const uint16_t price_) {
price = price_;
}
void Item::SetAsPlaythrough() {
playthrough = true;
}
void Item::SetCustomDrawFunc(const CustomDrawFunc drawFunc) const {
giEntry->drawFunc = drawFunc;
}
bool Item::IsPlaythrough() const {
return playthrough;
}
bool Item::IsBottleItem() const {
return getItemId == 0x0F || // Empty Bottle
getItemId == 0X14 || // Bottle with Milk
(getItemId >= 0x8C && getItemId <= 0x94); // Rest of bottled contents
}
bool Item::IsMajorItem() const {
const auto ctx = Context::GetInstance();
if (type == ITEMTYPE_TOKEN) {
return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS) || ctx->LACSCondition() == RO_LACS_TOKENS;
}
if (type == ITEMTYPE_DROP || type == ITEMTYPE_EVENT || type == ITEMTYPE_SHOP || type == ITEMTYPE_MAP ||
type == ITEMTYPE_COMPASS) {
return false;
}
if (type == ITEMTYPE_DUNGEONREWARD &&
ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) {
return false;
}
if ((randomizerGet == RG_BOMBCHU_5 || randomizerGet == RG_BOMBCHU_10 || randomizerGet == RG_BOMBCHU_20) &&
ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_NONE)) {
return false;
}
if (randomizerGet == RG_HEART_CONTAINER || randomizerGet == RG_PIECE_OF_HEART ||
randomizerGet == RG_TREASURE_GAME_HEART) {
return false;
}
if (type == ITEMTYPE_SMALLKEY && (ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA) ||
ctx->GetOption(RSK_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON))) {
return false;
}
if (type == ITEMTYPE_FORTRESS_SMALLKEY && ctx->GetOption(RSK_GERUDO_KEYS).Is(RO_GERUDO_KEYS_VANILLA)) {
return false;
}
if (type == ITEMTYPE_BOSSKEY && getItemId != 0xAD &&
(ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_VANILLA) ||
ctx->GetOption(RSK_BOSS_KEYSANITY).Is(RO_DUNGEON_ITEM_LOC_OWN_DUNGEON))) {
return false;
}
// Ganons Castle Boss Key
if (getItemId == 0xAD && (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_VANILLA) ||
ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_OWN_DUNGEON))) {
return false;
}
if (randomizerGet == RG_GREG_RUPEE) {
return ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG);
}
return IsAdvancement();
}
RandomizerHintTextKey Item::GetHintKey() const {
return hintKey;
}
const HintText& Item::GetHint() const {
return StaticData::hintTextTable[hintKey];
}
GetItemCategory Item::GetCategory() {
return category;
}
bool Item::operator==(const Item& right) const {
return type == right.GetItemType() && getItemId == right.GetItemID();
}
bool Item::operator!=(const Item& right) const {
return !operator==(right);
}
} // namespace Rando