Add OoT Static Hint, Fix Sheik Crashes, Add multiple text boxes to AutoFormat (#4149)

* add OoT hint, sheik is crashing without hint due to an unrelated bug

* Fix Sheik crashes, add new textbox to autoFormat, trim OoTHint text

* save pls

* fix receive typos

* git pls

* nice conflict fix
This commit is contained in:
Pepper0ni 2024-07-06 01:16:56 +01:00 committed by GitHub
parent 1ca8d0ab73
commit f88883044e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 85 additions and 32 deletions

View File

@ -314,7 +314,6 @@ void CustomMessage::CleanString(std::string& str) const {
static size_t NextLineLength(const std::string* textStr, const size_t lastNewline, bool hasIcon = false) {
const size_t maxLinePixelWidth = hasIcon ? 200 : 216;
size_t totalPixelWidth = 0;
size_t currentPos = lastNewline;
@ -363,24 +362,53 @@ void CustomMessage::AutoFormatString(std::string& str) const {// did I do this r
size_t lastNewline = 0;
const bool hasIcon = str.find('$', 0) != std::string::npos;
size_t lineLength = NextLineLength(&str, lastNewline, hasIcon);
size_t lineCount = 1;
while (lastNewline + lineLength < str.length()) {
const size_t carrot = str.find('^', lastNewline);
const size_t ampersand = str.find('&', lastNewline);
const size_t lastSpace = str.rfind(' ', lastNewline + lineLength);
const size_t lastPeriod = str.rfind('.', lastNewline + lineLength);
// replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
lastNewline = ampersand + 1;
// or move the lastNewline cursor to the next line if a '^' is encountered
} else if (carrot < lastNewline + lineLength) {
lastNewline = carrot + 1;
// some lines need to be split but don't have spaces, look for periods instead
} else if (lastSpace == std::string::npos) {
str.replace(lastPeriod, 1, ".&");
lastNewline = lastPeriod + 2;
if (lineCount < 4){
// replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
lastNewline = ampersand + 1;
// or move the lastNewline cursor to the next line if a '^' is encountered
} else if (carrot < lastNewline + lineLength) {
lastNewline = carrot + 1;
lineCount = 0;
// some lines need to be split but don't have spaces, look for periods instead
} else if (lastSpace == std::string::npos) {
const size_t lastPeriod = str.rfind('.', lastNewline + lineLength);
str.replace(lastPeriod, 1, ".&");
lastNewline = lastPeriod + 2;
} else {
str.replace(lastSpace, 1, "&");
lastNewline = lastSpace + 1;
}
lineCount += 1;
} else {
str.replace(lastSpace, 1, "&");
lastNewline = lastSpace + 1;
const size_t lastColor = str.rfind("\x05"s, lastNewline + lineLength);
std::string colorText = "";
//check if we are on a non default colour, as ^ resets it, and readd if needed
if (lastColor != std::string::npos && str[lastColor+1] != 0){
colorText = "\x05"s + str[lastColor+1];
}
// replace '&' first if it's within the newline range
if (ampersand < lastNewline + lineLength) {
str.replace(ampersand, 1, "^" + colorText);
lastNewline = ampersand + 1;
// or move the lastNewline cursor to the next line if a '^' is encountered.
} else if (carrot < lastNewline + lineLength) {
lastNewline = carrot + 1;
// some lines need to be split but don't have spaces, look for periods instead
} else if (lastSpace == std::string::npos) {
const size_t lastPeriod = str.rfind('.', lastNewline + lineLength);
str.replace(lastPeriod, 1, ".^" + colorText);
lastNewline = lastPeriod + 2;
} else {
str.replace(lastSpace, 1, "^" + colorText);
lastNewline = lastSpace + 1;
}
lineCount = 1;
}
lineLength = NextLineLength(&str, lastNewline, hasIcon);
}

View File

@ -504,6 +504,7 @@ const std::vector<const char*> randomizerCvars = {
CVAR_RANDOMIZER_SETTING("SariaHint"),
CVAR_RANDOMIZER_ENHANCEMENT("RandomizeRupeeNames"),
CVAR_RANDOMIZER_SETTING("FrogsHint"),
CVAR_RANDOMIZER_SETTING("OoTHint"),
CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi"),
CVAR_RANDOMIZER_ENHANCEMENT("QuestItemFanfares"),
};

View File

@ -2856,6 +2856,9 @@ void StaticData::HintTable_Init() {
/*french*/ "Des grenouilles se trouvant sous l'eau vous fixent attentivement, tenant fermement #[[1]]#.",
{QM_GREEN}));
hintTextTable[RHT_OOT_HINT] = HintText(CustomMessage("Bring the #Spiritual Stones# to the past so you can receive #[[1]]# from Zelda and learn #[[2]]#!",
{QM_BLUE, QM_GREEN, QM_GREEN}));
hintTextTable[RHT_SKULLS_HINT] = HintText(CustomMessage("Yeaaarrgh! I'm cursed!!^Please save me by destroying #[[d]] Spiders of the Curse# and I will give you my #[[1]]#!",
/*german*/ "Yeaaarrgh! Ich bin verflucht!^Bitte rette mich, indem du #[[d]] Skulltulas# zerstörst und ich werde dir dafür #[[1]]# geben!",
/*french*/ "Yeaaarrgh! Je suis maudit!^Détruit encore #[[d]] Araignées de la Malédiction# et j'aurai quelque chose à te donner! #([[1]])#",

View File

@ -279,7 +279,10 @@ std::vector<std::pair<RandomizerCheck, std::function<bool()>>> conditionalAlways
auto ctx = Rando::Context::GetInstance();
return !ctx->GetOption(RSK_MASK_SHOP_HINT) && !ctx->GetOption(RSK_COMPLETE_MASK_QUEST);
}),
std::make_pair(RC_SONG_FROM_OCARINA_OF_TIME, []() { return StonesRequiredBySettings() < 2; }),
std::make_pair(RC_SONG_FROM_OCARINA_OF_TIME, []() {
auto ctx = Rando::Context::GetInstance();
return StonesRequiredBySettings() < 2 && !ctx->GetOption(RSK_OOT_HINT);
}),
std::make_pair(RC_HF_OCARINA_OF_TIME_ITEM, []() { return StonesRequiredBySettings() < 2; }),
std::make_pair(RC_SHEIK_IN_KAKARIKO, []() { return MedallionsRequiredBySettings() < 5; }),
std::make_pair(RC_DMT_TRADE_CLAIM_CHECK, []() {

View File

@ -53,6 +53,7 @@ void AreaTable_Init_CastleTown() {
LocationAccess(RC_TOT_LIGHT_ARROWS_CUTSCENE, {[]{return logic->IsAdult && logic->CanTriggerLACS;}}),
LocationAccess(RC_ALTAR_HINT_CHILD, {[]{return logic->IsChild;}}),
LocationAccess(RC_ALTAR_HINT_ADULT, {[]{return logic->IsAdult;}}),
LocationAccess(RC_TOT_SHEIK_HINT, {[]{return logic->IsAdult;}}),
}, {
//Exits
Entrance(RR_TOT_ENTRANCE, {[]{return true;}}),

View File

@ -559,6 +559,7 @@ std::vector<RandomizerCheck> Rando::StaticData::staticHintLocations = {
RC_ALTAR_HINT_CHILD,
RC_ALTAR_HINT_ADULT,
RC_FISHING_POLE_HINT,
RC_TOT_SHEIK_HINT,
RC_MASK_SHOP_HINT,
};
@ -1565,7 +1566,8 @@ void Rando::StaticData::InitLocationTable() { //
locationTable[RC_ALTAR_HINT_CHILD] = Location::OtherHint(RC_ALTAR_HINT_CHILD, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_TEMPLE_OF_TIME, "ToT Child Altar Hint");
locationTable[RC_ALTAR_HINT_ADULT] = Location::OtherHint(RC_ALTAR_HINT_ADULT, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_TEMPLE_OF_TIME, "ToT Adult Altar Hint");
locationTable[RC_FISHING_POLE_HINT] = Location::OtherHint(RC_FISHING_POLE_HINT, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_LAKE_HYLIA, ACTOR_FISHING, SCENE_FISHING_POND, "Fishing Pole Hint");
locationTable[RC_MASK_SHOP_HINT] = Location::OtherHint(RC_MASK_SHOP_HINT, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_HAPPY_MASK_SHOP, "Mask Shop Hint");
locationTable[RC_TOT_SHEIK_HINT] = Location::OtherHint(RC_TOT_SHEIK_HINT, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_MARKET, ACTOR_EN_XC, SCENE_TEMPLE_OF_TIME, "Ocarina of Time Hint");
locationTable[RC_MASK_SHOP_HINT] = Location::OtherHint(RC_MASK_SHOP_HINT, RCQUEST_BOTH, RCTYPE_GOSSIP_STONE, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_HAPPY_MASK_SHOP, "Mask Shop Hint");
locationTable[RC_TRIFORCE_COMPLETED] = Location::Reward(RC_TRIFORCE_COMPLETED, RCQUEST_BOTH, RCTYPE_STANDARD, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, 0x00, "Completed Triforce", "Completed Triforce", RHT_NONE, RG_NONE, {}, SpoilerCollectionCheck::None(), SpoilerCollectionCheckGroup::GROUP_NO_GROUP);
// clang-format on

View File

@ -516,10 +516,11 @@ void Settings::CreateOptionDescriptions() {
mOptionDescriptions[RSK_SARIA_HINT] = "Talking to Saria either in person or through Saria's Song will tell you the "
"location of a progressive magic meter.";
mOptionDescriptions[RSK_FISHING_POLE_HINT] = "Talking to the fishing pond owner without the fishing pole will tell you its location.";
mOptionDescriptions[RSK_OOT_HINT] = "Sheik in the Temple of Time will tell you the item and song on the Ocarina of Time.";
mOptionDescriptions[RSK_FROGS_HINT] = "Standing near the pedestal for the frogs in Zora's River will tell you the "
"reward for the frogs' ocarina game.";
mOptionDescriptions[RSK_BIGGORON_HINT] = "Talking to Biggoron will tell you the item he will give you in exchange for the Claim Check.";
mOptionDescriptions[RSK_BIG_POES_HINT] = "Talking to the Poe Collector in the Market Guardhouse while adult will tell you what you recieve for handing in Big Poes.";
mOptionDescriptions[RSK_BIG_POES_HINT] = "Talking to the Poe Collector in the Market Guardhouse while adult will tell you what you receive for handing in Big Poes.";
mOptionDescriptions[RSK_CHICKENS_HINT] = "Talking to Anju as a child will tell you the item she will give you for delivering her Cuccos to the pen";
mOptionDescriptions[RSK_MALON_HINT] = "Talking to Malon as adult will tell you the item on \"Link's cow\", the cow you win from beating her time on the Lon Lon Obsticle Course.";
mOptionDescriptions[RSK_HBA_HINT] = "Talking to the Horseback Archery gerudo in Gerudo Fortress, or the nearby sign, will tell you what you win for scoring 1000 and 1500 points on Horseback Archery.";

View File

@ -2396,11 +2396,13 @@ CustomMessage Randomizer::GetSheikMessage(s16 scene, u16 originalTextId) {
CustomMessage messageEntry;
switch (scene) {
case SCENE_TEMPLE_OF_TIME:
if (!CHECK_DUNGEON_ITEM(DUNGEON_KEY_BOSS, SCENE_GANONS_TOWER)) {
if (ctx->GetOption(RSK_OOT_HINT) && !ctx->GetItemLocation(RC_SONG_FROM_OCARINA_OF_TIME)->HasObtained()){
messageEntry = ctx->GetHint(RH_OOT_HINT)->GetHintMessage(MF_RAW);
} else if (!CHECK_DUNGEON_ITEM(DUNGEON_KEY_BOSS, SCENE_GANONS_TOWER)) {
messageEntry = CustomMessage(
"@,&meet me at %gGanon's Castle%w&once you obtain the %rkey to his lair%w.",
"@, wir treffen uns bei %gGanons Schloß%w,&sobald Du den %rSchlüssel zu&seinem Verließ%w hast.",
"Retrouve-moi au %gChâteau de Ganon%w une&fois que tu auras obtenu la&Mrclé de son repaire%w.");
"@, meet me at %gGanon's Castle%w once you obtain the %rkey to his lair%w.",
"@, wir treffen uns bei %gGanons Schloß%w, sobald Du den %rSchlüssel zu seinem Verließ%w hast.",
"Retrouve-moi au %gChâteau de Ganon%w une fois que tu auras obtenu la Mrclé de son repaire%w.");
} else {
messageEntry = CustomMessage(
"The time has come. Prepare yourself.",
@ -2410,18 +2412,18 @@ CustomMessage Randomizer::GetSheikMessage(s16 scene, u16 originalTextId) {
break;
case SCENE_INSIDE_GANONS_CASTLE:
if (ctx->GetOption(RSK_SHEIK_LA_HINT) && INV_CONTENT(ITEM_ARROW_LIGHT) != ITEM_ARROW_LIGHT) {
messageEntry = ctx->GetHint(RH_SHEIK_HINT)->GetHintMessage(MF_AUTO_FORMAT);
messageEntry = ctx->GetHint(RH_SHEIK_HINT)->GetHintMessage(MF_RAW);
} else if (!(CHECK_OWNED_EQUIP(EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER) && INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT &&
CUR_CAPACITY(UPG_QUIVER) >= 30 && gSaveContext.isMagicAcquired)) {
messageEntry = CustomMessage("You are still ill-equipped to&face %rGanondorf%w."
"^Seek out the %cMaster Sword%w,&%rsomething to hold your arrows%w,&and %gmagic%w to summon the %ylight%w.",
"Du bist noch nicht gewappnet um Dich&%rGanondorf%w stellen zu können.^"
"Begib Dich auf die Suche nach dem&%cMaster-Schwert%w, %retwas um deine Pfeilen&einen Sinn zu geben%w,^sowie %gdie Magie%w, um das %yLicht%w&herauf beschwören zu können.",
"@, tu n'es toujours pas prêt à affronter&%rGanondorf%w.^"
"Cherche l'%cÉpée de Légende%w,&%rquelque chose pour ranger tes flèches%w&et de la %gmagie%w pour invoquer la&%ylumière%w.");
} else if (!Flags_GetEventChkInf(EVENTCHKINF_DISPELLED_GANONS_TOWER_BARRIER)){
messageEntry = CustomMessage("You are still ill-equipped to face %rGanondorf%w."
"^Seek out the %cMaster Sword%w, %rsomething to hold your arrows%w, and %gmagic%w to summon the %ylight%w.",
"Du bist noch nicht gewappnet um Dich %rGanondorf%w stellen zu können.^"
"Begib Dich auf die Suche nach dem %cMaster-Schwert%w, %retwas um deine Pfeilen einen Sinn zu geben%w,^sowie %gdie Magie%w, um das %yLicht%w herauf beschwören zu können.",
"@, tu n'es toujours pas prêt à affronter %rGanondorf%w.^"
"Cherche l'%cÉpée de Légende%w, %rquelque chose pour ranger tes flèches%w et de la %gmagie%w pour invoquer la %ylumière%w.");
} else if (!Flags_GetEventChkInf(EVENTCHKINF_DISPELLED_GANONS_TOWER_BARRIER) && !ctx->GetOption(RSK_TRIAL_COUNT).Is(0)){
messageEntry = CustomMessage(
"You may have what you need to defeat&%rthe Evil King%w, but the %cbarrier%w still&stands.^Complete the remaining %gtrials%w&to destroy it."
"You may have what you need to defeat %rthe Evil King%w, but the %cbarrier%w still stands.^Complete the remaining %gtrials%w to destroy it."
);
} else {
messageEntry = CustomMessage(
@ -2431,7 +2433,8 @@ CustomMessage Randomizer::GetSheikMessage(s16 scene, u16 originalTextId) {
}
break;
}
return messageEntry;
messageEntry.AutoFormat();
return messageEntry;
}
CustomMessage Randomizer::GetFishingPondOwnerMessage(u16 originalTextId) {

View File

@ -1489,6 +1489,7 @@ typedef enum {
RC_ALTAR_HINT_CHILD,
RC_ALTAR_HINT_ADULT,
RC_FISHING_POLE_HINT,
RC_TOT_SHEIK_HINT,
RC_MASK_SHOP_HINT,
RC_DMC_UPPER_GROTTO_FISH,
RC_DMT_STORMS_GROTTO_FISH,
@ -2081,6 +2082,7 @@ typedef enum {
RH_BIG_POES_HINT,
RH_BIGGORON_HINT,
RH_FROGS_HINT,
RH_OOT_HINT,
RH_KAK_10_SKULLS_HINT,
RH_KAK_20_SKULLS_HINT,
RH_KAK_30_SKULLS_HINT,
@ -3448,6 +3450,7 @@ typedef enum {
RHT_BIG_POES_HINT,
RHT_BIGGORON_HINT,
RHT_FROGS_HINT,
RHT_OOT_HINT,
RHT_SKULLS_HINT,
RHT_MASK_SHOP_HINT,
// Ganon Line
@ -3604,6 +3607,7 @@ typedef enum {
RSK_GREG_HINT,
RSK_SARIA_HINT,
RSK_FROGS_HINT,
RSK_OOT_HINT,
RSK_KAK_10_SKULLS_HINT,
RSK_KAK_20_SKULLS_HINT,
RSK_KAK_30_SKULLS_HINT,

View File

@ -164,6 +164,7 @@ void Settings::CreateOptions() {
mOptions[RSK_SARIA_HINT] = Option::Bool("Saria's Hint", CVAR_RANDOMIZER_SETTING("SariaHint"), mOptionDescriptions[RSK_SARIA_HINT], IMFLAG_NONE);
mOptions[RSK_FISHING_POLE_HINT] = Option::Bool("Fishing Pole Hint", CVAR_RANDOMIZER_SETTING("FishingPoleHint"), mOptionDescriptions[RSK_FISHING_POLE_HINT], IMFLAG_NONE);
mOptions[RSK_FROGS_HINT] = Option::Bool("Frog Ocarina Game Hint", CVAR_RANDOMIZER_SETTING("FrogsHint"), mOptionDescriptions[RSK_FROGS_HINT], IMFLAG_NONE);
mOptions[RSK_OOT_HINT] = Option::Bool("Ocarina of Time Hint", CVAR_RANDOMIZER_SETTING("OoTHint"), mOptionDescriptions[RSK_OOT_HINT], IMFLAG_NONE);
mOptions[RSK_BIGGORON_HINT] = Option::Bool("Biggoron's Hint", CVAR_RANDOMIZER_SETTING("BiggoronHint"), mOptionDescriptions[RSK_BIGGORON_HINT], IMFLAG_NONE);
mOptions[RSK_BIG_POES_HINT] = Option::Bool("Big Poes Hint", CVAR_RANDOMIZER_SETTING("BigPoesHint"), mOptionDescriptions[RSK_BIG_POES_HINT], IMFLAG_NONE);
mOptions[RSK_CHICKENS_HINT] = Option::Bool("Chickens Hint", CVAR_RANDOMIZER_SETTING("ChickensHint"), mOptionDescriptions[RSK_CHICKENS_HINT], IMFLAG_NONE);
@ -733,6 +734,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_GREG_HINT],
&mOptions[RSK_SARIA_HINT],
&mOptions[RSK_FROGS_HINT],
&mOptions[RSK_OOT_HINT],
&mOptions[RSK_BIGGORON_HINT],
&mOptions[RSK_BIG_POES_HINT],
&mOptions[RSK_CHICKENS_HINT],
@ -964,6 +966,7 @@ void Settings::CreateOptions() {
&mOptions[RSK_GREG_HINT],
&mOptions[RSK_SARIA_HINT],
&mOptions[RSK_FROGS_HINT],
&mOptions[RSK_OOT_HINT],
&mOptions[RSK_WARP_SONG_HINTS],
&mOptions[RSK_BIGGORON_HINT],
&mOptions[RSK_BIG_POES_HINT],
@ -1183,6 +1186,7 @@ void Settings::CreateOptions() {
{ "Miscellaneous Settings:Greg the Rupee Hint", RSK_GREG_HINT },
{ "Miscellaneous Settings:Saria's Hint", RSK_SARIA_HINT },
{ "Miscellaneous Settings:Frog Ocarina Game Hint", RSK_FROGS_HINT },
{ "Miscellaneous Settings:Ocarina of Time Hint", RSK_OOT_HINT },
{ "Miscellaneous Settings:10 GS Hint", RSK_KAK_10_SKULLS_HINT },
{ "Miscellaneous Settings:20 GS Hint", RSK_KAK_20_SKULLS_HINT },
{ "Miscellaneous Settings:30 GS Hint", RSK_KAK_30_SKULLS_HINT },
@ -2293,6 +2297,7 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) {
case RSK_GREG_HINT:
case RSK_SARIA_HINT:
case RSK_FROGS_HINT:
case RSK_OOT_HINT:
case RSK_KAK_10_SKULLS_HINT:
case RSK_KAK_20_SKULLS_HINT:
case RSK_KAK_30_SKULLS_HINT:

View File

@ -88,6 +88,7 @@ std::unordered_map<uint32_t, CustomMessage> StaticData::hintNames = {
{RH_BIG_POES_HINT, CustomMessage("Big Poe Reward Hint")},
{RH_BIGGORON_HINT, CustomMessage("Biggoron Claim Check Hint")},
{RH_FROGS_HINT, CustomMessage("Final Frogs in River Hint")},
{RH_OOT_HINT, CustomMessage("Sheik in Temple of Time Hint")},
{RH_KAK_10_SKULLS_HINT, CustomMessage("10 Skulls Hint")},
{RH_KAK_20_SKULLS_HINT, CustomMessage("20 Skulls Hint")},
{RH_KAK_30_SKULLS_HINT, CustomMessage("30 Skulls Hint")},
@ -209,6 +210,7 @@ std::unordered_map<RandomizerHint, StaticHintInfo> StaticData::staticHintInfoMap
{RH_CHICKENS_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_CHICKENS_HINT}, RSK_CHICKENS_HINT, true, {RC_KAK_ANJU_AS_CHILD})},
{RH_BIGGORON_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_BIGGORON_HINT}, RSK_BIGGORON_HINT, true, {RC_DMT_TRADE_CLAIM_CHECK})},
{RH_FROGS_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_FROGS_HINT}, RSK_FROGS_HINT, true, {RC_ZR_FROGS_OCARINA_GAME})},
{RH_OOT_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_OOT_HINT}, RSK_OOT_HINT, true, {RC_HF_OCARINA_OF_TIME_ITEM, RC_SONG_FROM_OCARINA_OF_TIME}, {}, {RC_TOT_SHEIK_HINT})},
{RH_KAK_10_SKULLS_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_SKULLS_HINT}, RSK_KAK_10_SKULLS_HINT, true, {RC_KAK_10_GOLD_SKULLTULA_REWARD}, {}, {}, false, 10)},
{RH_KAK_20_SKULLS_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_SKULLS_HINT}, RSK_KAK_20_SKULLS_HINT, true, {RC_KAK_20_GOLD_SKULLTULA_REWARD}, {}, {}, false, 20)},
{RH_KAK_30_SKULLS_HINT, StaticHintInfo(HINT_TYPE_ITEM, {RHT_SKULLS_HINT}, RSK_KAK_30_SKULLS_HINT, true, {RC_KAK_30_GOLD_SKULLTULA_REWARD}, {}, {}, false, 30)},