762 lines
32 KiB
C++
762 lines
32 KiB
C++
#include "hints.hpp"
|
|
|
|
#include "item_pool.hpp"
|
|
#include "random.hpp"
|
|
#include "spoiler_log.hpp"
|
|
#include "fill.hpp"
|
|
#include "../trial.h"
|
|
#include "../entrance.h"
|
|
#include "z64item.h"
|
|
#include <spdlog/spdlog.h>
|
|
#include "../randomizerTypes.h"
|
|
#include "pool_functions.hpp"
|
|
#include "../hint.h"
|
|
#include "../static_data.h"
|
|
|
|
|
|
using namespace Rando;
|
|
|
|
HintDistributionSetting::HintDistributionSetting(std::string _name,
|
|
HintType _type,
|
|
uint32_t _weight,
|
|
uint8_t _fixed,
|
|
uint8_t _copies,
|
|
std::function<bool(RandomizerCheck)> _filter,
|
|
uint8_t _dungeonLimit){
|
|
name = _name;
|
|
type = _type;
|
|
weight = _weight;
|
|
fixed = _fixed;
|
|
copies = _copies;
|
|
filter = _filter;
|
|
dungeonLimit = _dungeonLimit;
|
|
}
|
|
|
|
HintText::HintText(CustomMessage clearText_, std::vector<CustomMessage> ambiguousText_, std::vector<CustomMessage> obscureText_)
|
|
: clearText(std::move(clearText_)), ambiguousText(std::move(ambiguousText_)), obscureText(std::move(obscureText_)){}
|
|
|
|
const CustomMessage& HintText::GetClear() const {
|
|
return clearText;
|
|
}
|
|
|
|
const CustomMessage& HintText::GetObscure() const {
|
|
return obscureText.size() > 0 ? RandomElement(obscureText) : clearText;
|
|
}
|
|
|
|
const CustomMessage& HintText::GetObscure(uint8_t selection) const {
|
|
if (obscureText.size() > selection){
|
|
return obscureText[selection];
|
|
} else if (obscureText.size() > 0) {
|
|
return obscureText[0];
|
|
}
|
|
return clearText;
|
|
}
|
|
|
|
const CustomMessage& HintText::GetAmbiguous() const {
|
|
return ambiguousText.size() > 0 ? RandomElement(ambiguousText) : clearText;
|
|
}
|
|
|
|
const CustomMessage& HintText::GetAmbiguous(uint8_t selection) const {
|
|
if (ambiguousText.size() > selection){
|
|
return ambiguousText[selection];
|
|
} else if (ambiguousText.size() > 0) {
|
|
return ambiguousText[0];
|
|
}
|
|
return clearText;
|
|
}
|
|
|
|
uint8_t HintText::GetAmbiguousSize() const{
|
|
return ambiguousText.size();
|
|
}
|
|
|
|
uint8_t HintText::GetObscureSize() const{
|
|
return obscureText.size();
|
|
}
|
|
|
|
const CustomMessage& HintText::GetMessage() const {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
if (ctx->GetOption(RSK_HINT_CLARITY).Is(RO_HINT_CLARITY_OBSCURE)) {
|
|
return GetObscure();
|
|
} else if (ctx->GetOption(RSK_HINT_CLARITY).Is(RO_HINT_CLARITY_AMBIGUOUS)) {
|
|
return GetAmbiguous();
|
|
} else {
|
|
return GetClear();
|
|
}
|
|
}
|
|
|
|
const CustomMessage HintText::GetMessageCopy() const {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
if (ctx->GetOption(RSK_HINT_CLARITY).Is(RO_HINT_CLARITY_OBSCURE)) {
|
|
return GetObscure();
|
|
} else if (ctx->GetOption(RSK_HINT_CLARITY).Is(RO_HINT_CLARITY_AMBIGUOUS)) {
|
|
return GetAmbiguous();
|
|
} else {
|
|
return GetClear();
|
|
}
|
|
}
|
|
|
|
bool HintText::operator==(const HintText& right) const {
|
|
return obscureText == right.obscureText &&
|
|
ambiguousText == right.ambiguousText &&
|
|
clearText == right.clearText;
|
|
}
|
|
|
|
bool HintText::operator!=(const HintText& right) const {
|
|
return !operator==(right);
|
|
}
|
|
|
|
StaticHintInfo::StaticHintInfo(HintType _type, std::vector<RandomizerHintTextKey> _hintKeys, RandomizerSettingKey _setting,
|
|
std::variant<bool, uint8_t> _condition, std::vector<RandomizerCheck> _targetChecks,
|
|
std::vector<RandomizerGet> _targetItems, std::vector<RandomizerCheck> _hintChecks, bool _yourPocket, int _num):
|
|
type(_type), hintKeys(_hintKeys), setting(_setting), condition(_condition), targetChecks(_targetChecks),
|
|
targetItems(_targetItems), hintChecks(_hintChecks), yourPocket(_yourPocket), num(_num){}
|
|
|
|
RandomizerHintTextKey GetRandomJunkHint(){
|
|
//temp code to handle random junk hints now I work in keys instead of a vector of HintText
|
|
// Will be replaced with a better system once more customisable hint pools are added
|
|
uint32_t range = RHT_JUNK_SG_8 - RHT_JUNK02;
|
|
return (RandomizerHintTextKey)(Random(0, range) + RHT_JUNK02);
|
|
}
|
|
|
|
RandomizerHintTextKey GetRandomGanonJoke(){
|
|
uint32_t range = RHT_GANON_JOKE11 - RHT_GANON_JOKE01;
|
|
return (RandomizerHintTextKey)(Random(0, range) + RHT_GANON_JOKE01);
|
|
}
|
|
|
|
|
|
bool FilterWotHLocations(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return ctx->GetItemLocation(loc)->IsWothCandidate();
|
|
}
|
|
|
|
bool FilterBarrenLocations(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return ctx->GetItemLocation(loc)->IsBarrenCandidate();
|
|
}
|
|
|
|
bool FilterSongLocations(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return Rando::StaticData::GetLocation(loc)->IsCategory(Category::cSong);
|
|
}
|
|
|
|
bool FilterOverworldLocations(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return Rando::StaticData::GetLocation(loc)->IsOverworld();
|
|
}
|
|
|
|
bool FilterDungeonLocations(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return Rando::StaticData::GetLocation(loc)->IsDungeon();
|
|
}
|
|
|
|
bool FilterGoodItems(RandomizerCheck loc){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return ctx->GetItemLocation(loc)->GetPlacedItem().IsMajorItem();
|
|
}
|
|
|
|
bool NoFilter(RandomizerCheck loc){
|
|
return true;
|
|
}
|
|
|
|
const std::array<HintSetting, 4> hintSettingTable{{
|
|
// Useless hints
|
|
{
|
|
.alwaysCopies = 0,
|
|
.trialCopies = 0,
|
|
.junkWeight = 1, //RANDOTODO when the hint pool is not implicitly an itemLocations, handle junk like other hint types
|
|
.distTable = {} /*RANDOTODO Instead of loading a function into this,
|
|
apply this filter on all possible hintables in advance and then filter by what is acually in the seed at the start of generation.
|
|
This allows the distTable to hold the current status in hint generation (reducing potential doubled work) and
|
|
will make handling custom hint pools easier later*/
|
|
},
|
|
// Balanced hints
|
|
{
|
|
.alwaysCopies = 1,
|
|
.trialCopies = 1,
|
|
.junkWeight = 6,
|
|
.distTable = {
|
|
{"WotH", HINT_TYPE_WOTH, 7, 0, 1, FilterWotHLocations, 2},
|
|
{"Barren", HINT_TYPE_FOOLISH, 4, 0, 1, FilterBarrenLocations, 1},
|
|
//("Entrance", HINT_TYPE_ENTRANCE, 6, 0, 1), //not yet implemented
|
|
{"Song", HINT_TYPE_ITEM, 2, 0, 1, FilterSongLocations},
|
|
{"Overworld", HINT_TYPE_ITEM, 4, 0, 1, FilterOverworldLocations},
|
|
{"Dungeon", HINT_TYPE_ITEM, 3, 0, 1, FilterDungeonLocations},
|
|
{"Named Item", HINT_TYPE_ITEM_AREA, 10, 0, 1, FilterGoodItems},
|
|
{"Random" , HINT_TYPE_ITEM_AREA, 12, 0, 1, NoFilter}
|
|
}
|
|
},
|
|
// Strong hints
|
|
{
|
|
.alwaysCopies = 2,
|
|
.trialCopies = 1,
|
|
.junkWeight = 0,
|
|
.distTable = {
|
|
{"WotH", HINT_TYPE_WOTH, 12, 0, 2, FilterWotHLocations, 2},
|
|
{"Barren", HINT_TYPE_FOOLISH, 12, 0, 1, FilterBarrenLocations, 1},
|
|
//{"Entrance", HINT_TYPE_ENTRANCE, 4, 0, 1}, //not yet implemented
|
|
{"Song", HINT_TYPE_ITEM, 4, 0, 1, FilterSongLocations},
|
|
{"Overworld", HINT_TYPE_ITEM, 6, 0, 1, FilterOverworldLocations},
|
|
{"Dungeon", HINT_TYPE_ITEM, 6, 0, 1, FilterDungeonLocations},
|
|
{"Named Item", HINT_TYPE_ITEM_AREA, 8, 0, 1, FilterGoodItems},
|
|
{"Random" , HINT_TYPE_ITEM_AREA, 8, 0, 1, NoFilter},
|
|
},
|
|
},
|
|
// Very strong hints
|
|
{
|
|
.alwaysCopies = 2,
|
|
.trialCopies = 1,
|
|
.junkWeight = 0,
|
|
.distTable = {
|
|
{"WotH", HINT_TYPE_WOTH, 15, 0, 2, FilterWotHLocations},
|
|
{"Barren", HINT_TYPE_FOOLISH, 15, 0, 1, FilterBarrenLocations},
|
|
//{"Entrance", HINT_TYPE_ENTRANCE, 10, 0, 1}, //not yet implemented
|
|
{"Song", HINT_TYPE_ITEM, 2, 0, 1, FilterSongLocations},
|
|
{"Overworld", HINT_TYPE_ITEM, 7, 0, 1, FilterOverworldLocations},
|
|
{"Dungeon", HINT_TYPE_ITEM, 7, 0, 1, FilterDungeonLocations},
|
|
{"Named Item", HINT_TYPE_ITEM_AREA, 5, 0, 1, FilterGoodItems},
|
|
},
|
|
},
|
|
}};
|
|
|
|
uint8_t StonesRequiredBySettings() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
uint8_t stones = 0;
|
|
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_STONES)) {
|
|
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_STONE_COUNT).Value<uint8_t>();
|
|
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) {
|
|
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>() - 6;
|
|
} else if ((ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS)) && (ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON))) {
|
|
stones = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>() - 6;
|
|
}
|
|
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) {
|
|
stones = std::max<uint8_t>({ stones, ctx->GetOption(RSK_LACS_STONE_COUNT).Value<uint8_t>() });
|
|
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_STONES)) {
|
|
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>() - 6 )});
|
|
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS)) {
|
|
stones = std::max<uint8_t>({ stones, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>() - 6 )});
|
|
}
|
|
return stones;
|
|
}
|
|
|
|
uint8_t MedallionsRequiredBySettings() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
uint8_t medallions = 0;
|
|
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_MEDALLIONS)) {
|
|
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_MEDALLION_COUNT).Value<uint8_t>();
|
|
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEON_REWARDS)) {
|
|
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_REWARD_COUNT).Value<uint8_t>() - 3;
|
|
} else if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) {
|
|
medallions = ctx->GetOption(RSK_RAINBOW_BRIDGE_DUNGEON_COUNT).Value<uint8_t>() - 3;
|
|
}
|
|
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_MEDALLIONS)) {
|
|
medallions = std::max({ medallions, ctx->GetOption(RSK_LACS_MEDALLION_COUNT).Value<uint8_t>() });
|
|
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_REWARDS)) {
|
|
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_REWARD_COUNT).Value<uint8_t>() - 3 )});
|
|
} else if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_DUNGEONS) && ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON)) {
|
|
medallions = std::max({ medallions, (uint8_t)(ctx->GetOption(RSK_LACS_DUNGEON_COUNT).Value<uint8_t>() - 3 )});
|
|
}
|
|
return medallions;
|
|
}
|
|
|
|
uint8_t TokensRequiredBySettings() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
uint8_t tokens = 0;
|
|
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_TOKENS)) {
|
|
tokens = ctx->GetOption(RSK_RAINBOW_BRIDGE_TOKEN_COUNT).Value<uint8_t>();
|
|
}
|
|
if (ctx->GetOption(RSK_GANONS_BOSS_KEY).Is(RO_GANON_BOSS_KEY_LACS_TOKENS)) {
|
|
tokens = std::max<uint8_t>({ tokens, ctx->GetOption(RSK_LACS_TOKEN_COUNT).Value<uint8_t>() });
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
std::vector<std::pair<RandomizerCheck, std::function<bool()>>> conditionalAlwaysHints = {
|
|
std::make_pair(RC_MARKET_10_BIG_POES, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return ctx->GetOption(RSK_BIG_POE_COUNT).Value<uint8_t>() >= 3 && !ctx->GetOption(RSK_BIG_POES_HINT);
|
|
}), // Remember, the option's value being 3 means 4 are required
|
|
std::make_pair(RC_DEKU_THEATER_MASK_OF_TRUTH, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_COMPLETE_MASK_QUEST);
|
|
}),
|
|
std::make_pair(RC_SONG_FROM_OCARINA_OF_TIME, []() { return StonesRequiredBySettings() < 2; }),
|
|
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, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_BIGGORON_HINT);
|
|
}),
|
|
std::make_pair(RC_KAK_30_GOLD_SKULLTULA_REWARD, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_KAK_30_SKULLS_HINT) && TokensRequiredBySettings() < 30;
|
|
}),
|
|
std::make_pair(RC_KAK_40_GOLD_SKULLTULA_REWARD, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_KAK_40_SKULLS_HINT) && TokensRequiredBySettings() < 40;
|
|
}),
|
|
std::make_pair(RC_KAK_50_GOLD_SKULLTULA_REWARD, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_KAK_50_SKULLS_HINT) && TokensRequiredBySettings() < 50;
|
|
}),
|
|
std::make_pair(RC_ZR_FROGS_OCARINA_GAME, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_FROGS_HINT);
|
|
}),
|
|
std::make_pair(RC_KF_LINKS_HOUSE_COW, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_MALON_HINT);
|
|
}),
|
|
std::make_pair(RC_KAK_100_GOLD_SKULLTULA_REWARD, []() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return !ctx->GetOption(RSK_KAK_100_SKULLS_HINT) && TokensRequiredBySettings() < 100;
|
|
}),
|
|
};
|
|
|
|
static std::vector<RandomizerCheck> GetEmptyGossipStones() {
|
|
auto emptyGossipStones = GetEmptyLocations(Rando::StaticData::gossipStoneLocations);
|
|
return emptyGossipStones;
|
|
}
|
|
|
|
static std::vector<RandomizerCheck> GetAccessibleGossipStones(const RandomizerCheck hintedLocation = RC_GANON) {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
//temporarily remove the hinted location's item, and then perform a
|
|
//reachability search for gossip stone locations.
|
|
RandomizerGet originalItem = ctx->GetItemLocation(hintedLocation)->GetPlacedRandomizerGet();
|
|
ctx->GetItemLocation(hintedLocation)->SetPlacedItem(RG_NONE);
|
|
|
|
ctx->GetLogic()->Reset();
|
|
auto accessibleGossipStones = GetAccessibleLocations(Rando::StaticData::gossipStoneLocations);
|
|
//Give the item back to the location
|
|
ctx->GetItemLocation(hintedLocation)->SetPlacedItem(originalItem);
|
|
|
|
return accessibleGossipStones;
|
|
}
|
|
|
|
bool IsReachableWithout(std::vector<RandomizerCheck> locsToCheck, RandomizerCheck excludedCheck, bool resetAfter = true){
|
|
//temporarily remove the hinted location's item, and then perform a
|
|
//reachability search for this check RANDOTODO convert excludedCheck to an ItemLocation
|
|
auto ctx = Rando::Context::GetInstance();
|
|
RandomizerGet originalItem = ctx->GetItemLocation(excludedCheck)->GetPlacedRandomizerGet();
|
|
ctx->GetItemLocation(excludedCheck)->SetPlacedItem(RG_NONE);
|
|
ctx->GetLogic()->Reset();
|
|
const auto rechableWithout = GetAccessibleLocations(locsToCheck);
|
|
ctx->GetItemLocation(excludedCheck)->SetPlacedItem(originalItem);
|
|
if (resetAfter){
|
|
//if resetAfter is on, reset logic we are done
|
|
ctx->GetLogic()->Reset();
|
|
}
|
|
if (rechableWithout.empty()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void SetAllInRegionAsHinted(RandomizerArea area, std::vector<RandomizerCheck> locations){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
std::vector<RandomizerCheck> locsInRegion = FilterFromPool(locations, [area, ctx](const RandomizerCheck loc){
|
|
return ctx->GetItemLocation(loc)->GetArea() == area;
|
|
});
|
|
for(RandomizerCheck loc: locsInRegion){
|
|
ctx->GetItemLocation(loc)->SetHintAccesible();
|
|
}
|
|
}
|
|
|
|
static void AddGossipStoneHint( const RandomizerCheck gossipStone,
|
|
const HintType hintType,
|
|
const std::string distributionName,
|
|
const std::vector<RandomizerHintTextKey> hintKeys,
|
|
const std::vector<RandomizerCheck> locations,
|
|
const std::vector<TrialKey> trials) {
|
|
|
|
auto ctx = Rando::Context::GetInstance();
|
|
ctx->AddHint(StaticData::gossipStoneCheckToHint[gossipStone], Hint(StaticData::gossipStoneCheckToHint[gossipStone], hintType, distributionName, hintKeys, locations, {}, trials));
|
|
ctx->GetItemLocation(gossipStone)->SetPlacedItem(RG_HINT); //RANDOTODO, better gossip stone to location to hint key system
|
|
}
|
|
|
|
static void AddGossipStoneHintCopies(uint8_t copies,
|
|
const HintType hintType,
|
|
const std::string distributionName,
|
|
const std::vector<RandomizerHintTextKey> hintKeys = {},
|
|
const std::vector<RandomizerCheck> locations = {},
|
|
const std::vector<TrialKey> trials = {},
|
|
RandomizerCheck firstStone = RC_UNKNOWN_CHECK){
|
|
|
|
if (firstStone != RC_UNKNOWN_CHECK && copies > 0){
|
|
AddGossipStoneHint(firstStone, hintType, distributionName, hintKeys, locations, trials);
|
|
copies -= 1;
|
|
}
|
|
for(int c=0; c<copies; c++){
|
|
//get a random gossip stone
|
|
auto gossipStones = GetEmptyGossipStones();
|
|
if (gossipStones.empty()) {
|
|
SPDLOG_DEBUG("\tNO GOSSIP STONES TO PLACE HINT\n\n");
|
|
return;
|
|
}
|
|
auto gossipStone = RandomElement(gossipStones, false);
|
|
AddGossipStoneHint(gossipStone, hintType, distributionName, hintKeys, locations, trials);
|
|
}
|
|
}
|
|
|
|
static bool CreateHint(RandomizerCheck location, uint8_t copies, HintType type, std::string distribution){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
|
|
//get a gossip stone accessible without the hinted item
|
|
std::vector<RandomizerCheck> gossipStoneLocations = GetAccessibleGossipStones(location);
|
|
if (gossipStoneLocations.empty()) {
|
|
SPDLOG_DEBUG("\tNO IN LOGIC GOSSIP STONE\n\n");
|
|
return false;
|
|
}
|
|
RandomizerCheck gossipStone = RandomElement(gossipStoneLocations);
|
|
|
|
//make hint text
|
|
|
|
ctx->GetItemLocation(location)->SetHintAccesible();
|
|
|
|
AddGossipStoneHintCopies(copies, type, distribution, {}, {location}, {}, gossipStone);
|
|
return true;
|
|
}
|
|
|
|
|
|
static RandomizerCheck CreateRandomHint(std::vector<RandomizerCheck>& possibleHintLocations,
|
|
uint8_t copies,
|
|
HintType type,
|
|
std::string distributionName) {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
|
|
//return if there aren't any hintable locations or gossip stones available
|
|
if (GetEmptyGossipStones().size() < copies) {
|
|
SPDLOG_DEBUG("\tNOT ENOUGH GOSSIP STONES TO PLACE HINTS\n\n");
|
|
return RC_UNKNOWN_CHECK;
|
|
}
|
|
|
|
RandomizerCheck hintedLocation;
|
|
bool placed = false;
|
|
while (!placed){
|
|
if (possibleHintLocations.empty()) {
|
|
SPDLOG_DEBUG("\tNO LOCATIONS TO HINT\n\n");
|
|
return RC_UNKNOWN_CHECK;
|
|
}
|
|
hintedLocation = RandomElement(possibleHintLocations, true); //removing the location to avoid it being hinted again on fail
|
|
|
|
SPDLOG_DEBUG("\tLocation: ");
|
|
SPDLOG_DEBUG(Rando::StaticData::GetLocation(hintedLocation)->GetName());
|
|
SPDLOG_DEBUG("\n");
|
|
|
|
SPDLOG_DEBUG("\tItem: ");
|
|
SPDLOG_DEBUG(ctx->GetItemLocation(hintedLocation)->GetPlacedItemName().GetEnglish());
|
|
SPDLOG_DEBUG("\n");
|
|
|
|
placed = CreateHint(hintedLocation, copies, type, distributionName);
|
|
}
|
|
if (type == HINT_TYPE_FOOLISH){
|
|
SetAllInRegionAsHinted(ctx->GetItemLocation(hintedLocation)->GetArea(), possibleHintLocations);
|
|
}
|
|
return hintedLocation;
|
|
}
|
|
|
|
static std::vector<RandomizerCheck> FilterHintability(std::vector<RandomizerCheck>& locations,
|
|
std::function<bool(RandomizerCheck)> extraFilter = NoFilter){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
return FilterFromPool(locations, [extraFilter, ctx](const RandomizerCheck loc) {
|
|
return ctx->GetItemLocation(loc)->IsHintable() && !(ctx->GetItemLocation(loc)->IsAHintAccessible())
|
|
&& extraFilter(loc);
|
|
});
|
|
}
|
|
|
|
static void CreateJunkHints(uint8_t numHints) {
|
|
for(uint8_t c = 0; c < numHints; c++){
|
|
//duplicate junk hints are possible for now
|
|
AddGossipStoneHintCopies(1, HINT_TYPE_HINT_KEY, "Junk", {GetRandomJunkHint()});
|
|
}
|
|
}
|
|
|
|
static void CreateTrialHints(uint8_t copies) {
|
|
if (copies > 0) {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
if (ctx->GetOption(RSK_TRIAL_COUNT).Is(6)) {//six trials
|
|
AddGossipStoneHintCopies(copies, HINT_TYPE_HINT_KEY, "Trial", {RHT_SIX_TRIALS});
|
|
} else if (ctx->GetOption(RSK_TRIAL_COUNT).Is(0)) {//zero trials
|
|
AddGossipStoneHintCopies(copies, HINT_TYPE_HINT_KEY, "Trial", {RHT_ZERO_TRIALS});
|
|
} else {
|
|
std::vector<TrialInfo*> trials = ctx->GetTrials()->GetTrialList(); //there's probably a way to remove this assignment
|
|
if (ctx->GetOption(RSK_TRIAL_COUNT).Value<uint8_t>() >= 4) {//4 or 5 required trials, get skipped trials
|
|
trials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsSkipped();});
|
|
} else {//1 to 3 trials, get requried trials
|
|
auto requiredTrials = FilterFromPool(trials, [](TrialInfo* trial){return trial->IsRequired();});
|
|
}
|
|
for (auto& trial : trials) {//create a hint for each hinted trial
|
|
AddGossipStoneHintCopies(copies, HINT_TYPE_TRIAL, "Trial", {}, {}, {trial->GetTrialKey()});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateWarpSongTexts() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
if (ctx->GetOption(RSK_SHUFFLE_WARP_SONGS)){
|
|
auto warpSongEntrances = GetShuffleableEntrances(EntranceType::WarpSong, false);
|
|
for (auto entrance : warpSongEntrances) {
|
|
auto destination = entrance->GetConnectedRegion()->GetArea();
|
|
switch (entrance->GetIndex()) {
|
|
case 0x0600: // minuet RANDOTODO make into entrance hints when they are added
|
|
ctx->AddHint(RH_MINUET_WARP_LOC, Hint(RH_MINUET_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
case 0x04F6: // bolero
|
|
ctx->AddHint(RH_BOLERO_WARP_LOC, Hint(RH_BOLERO_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
case 0x0604: // serenade
|
|
ctx->AddHint(RH_SERENADE_WARP_LOC, Hint(RH_SERENADE_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
case 0x01F1: // requiem
|
|
ctx->AddHint(RH_REQUIEM_WARP_LOC, Hint(RH_REQUIEM_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
case 0x0568: // nocturne
|
|
ctx->AddHint(RH_NOCTURNE_WARP_LOC, Hint(RH_NOCTURNE_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
case 0x05F4: // prelude
|
|
ctx->AddHint(RH_PRELUDE_WARP_LOC, Hint(RH_PRELUDE_WARP_LOC, HINT_TYPE_AREA, "", {RHT_WARP_SONG}, {}, {destination}));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t getRandomWeight(int32_t totalWeight){
|
|
if (totalWeight <= 1){
|
|
return 1;
|
|
}
|
|
return Random(1,totalWeight);
|
|
}
|
|
|
|
static void DistributeHints(std::vector<uint8_t>& selected, size_t stoneCount, std::vector<HintDistributionSetting> distTable, uint8_t junkWieght, bool addFixed = true){
|
|
int32_t totalWeight = junkWieght;
|
|
|
|
for (HintDistributionSetting setting: distTable){
|
|
totalWeight += setting.weight;
|
|
if (addFixed){
|
|
selected[setting.type] += setting.fixed;
|
|
stoneCount -= setting.fixed * setting.copies;
|
|
}
|
|
}
|
|
int32_t currentWeight = getRandomWeight(totalWeight);
|
|
while(stoneCount > 0 && totalWeight > 0){
|
|
for (uint8_t distribution = 0; distribution < distTable.size(); distribution++){
|
|
currentWeight -= distTable[distribution].weight;
|
|
if (currentWeight <= 0){
|
|
if (stoneCount >= distTable[distribution].copies || distTable[distribution].copies == 0){
|
|
selected[distribution] += 1;
|
|
stoneCount -= distTable[distribution].copies;
|
|
break;
|
|
} else {
|
|
totalWeight -= distTable[distribution].weight;
|
|
distTable[distribution].weight = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//if there's still weight, then it's junk
|
|
if (currentWeight > 0){
|
|
selected[selected.size()-1] += 1;
|
|
stoneCount -= 1;
|
|
}
|
|
currentWeight = getRandomWeight(totalWeight);
|
|
}
|
|
//if stones are left, assign junk
|
|
if (stoneCount > 0){
|
|
selected[selected.size()-1] += stoneCount;
|
|
}
|
|
}
|
|
|
|
uint8_t PlaceHints(std::vector<uint8_t>& selectedHints,
|
|
std::vector<HintDistributionSetting>& distTable){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
uint8_t curSlot = 0;
|
|
for (HintDistributionSetting distribution : distTable){
|
|
std::vector<RandomizerCheck> hintTypePool = FilterHintability(ctx->allLocations, distribution.filter);
|
|
for (uint8_t numHint = 0; numHint < selectedHints[curSlot]; numHint++){
|
|
|
|
SPDLOG_DEBUG("Attempting to make hint of type: " + StaticData::hintTypeNames[distribution.type].GetEnglish() + "\n");
|
|
RandomizerCheck hintedLocation = RC_UNKNOWN_CHECK;
|
|
|
|
hintedLocation = CreateRandomHint(hintTypePool, distribution.copies, distribution.type, distribution.name);
|
|
if (hintedLocation == RC_UNKNOWN_CHECK){ //if hint failed to place
|
|
uint8_t hintsToRemove = (selectedHints[curSlot] - numHint) * distribution.copies;
|
|
selectedHints[curSlot] = 0;
|
|
distTable[curSlot].copies = 0;
|
|
return hintsToRemove;
|
|
}
|
|
if(Rando::StaticData::GetLocation(hintedLocation)->IsDungeon()){
|
|
distribution.dungeonLimit -= 1;
|
|
if (distribution.dungeonLimit == 0){
|
|
FilterFromPool(hintTypePool, FilterOverworldLocations);
|
|
}
|
|
}
|
|
}
|
|
selectedHints[curSlot] = 0;
|
|
curSlot += 1;
|
|
}
|
|
CreateJunkHints(selectedHints[selectedHints.size() - 1]);
|
|
return 0;
|
|
}
|
|
|
|
void CreateStoneHints() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
SPDLOG_DEBUG("\nNOW CREATING HINTS\n");
|
|
const HintSetting& hintSetting = hintSettingTable[ctx->GetOption(RSK_HINT_DISTRIBUTION).Value<uint8_t>()];
|
|
std::vector<HintDistributionSetting> distTable = hintSetting.distTable;
|
|
|
|
// Apply impa's song exclusions when zelda is skipped
|
|
if(ctx->GetOption(RSK_SKIP_CHILD_ZELDA)){
|
|
ctx->GetItemLocation(RC_SONG_FROM_IMPA)->SetHintAccesible();
|
|
}
|
|
|
|
// Add 'always' location hints
|
|
std::vector<RandomizerCheck> alwaysHintLocations = {};
|
|
if (hintSetting.alwaysCopies > 0) {
|
|
if (ctx->GetOption(RSK_RAINBOW_BRIDGE).Is(RO_BRIDGE_GREG)){
|
|
// If we have Rainbow Bridge set to Greg and the greg hint isn't useful, add a hint for where Greg is
|
|
// Do we really need this with the greg hint?
|
|
auto gregLocations = FilterFromPool(ctx->allLocations, [ctx](const RandomizerCheck loc) {
|
|
return (
|
|
(ctx->GetItemLocation(loc)->GetPlacedRandomizerGet() == RG_GREG_RUPEE)) &&
|
|
ctx->GetItemLocation(loc)->IsHintable() &&
|
|
!(ctx->GetOption(RSK_GREG_HINT) && (IsReachableWithout({RC_GREG_HINT}, loc, true)));
|
|
});
|
|
if (gregLocations.size() > 0){
|
|
alwaysHintLocations.push_back(gregLocations[0]);
|
|
}
|
|
}
|
|
|
|
for (auto& hint : conditionalAlwaysHints) {
|
|
RandomizerCheck loc = hint.first;
|
|
if (hint.second() && ctx->GetItemLocation(loc)->IsHintable()) {
|
|
alwaysHintLocations.push_back(loc);
|
|
}
|
|
}
|
|
|
|
for (RandomizerCheck location : alwaysHintLocations) {
|
|
CreateHint(location, hintSetting.alwaysCopies, HINT_TYPE_ITEM, "Always");
|
|
}
|
|
}
|
|
|
|
//Add 'trial' location hints
|
|
if (ctx->GetOption(RSK_GANONS_TRIALS).IsNot(RO_GANONS_TRIALS_SKIP)) {
|
|
CreateTrialHints(hintSetting.trialCopies);
|
|
}
|
|
|
|
size_t totalStones = GetEmptyGossipStones().size();
|
|
std::vector<uint8_t> selectedHints = {};
|
|
for (uint8_t c=0; c < distTable.size(); c++){
|
|
selectedHints.push_back(0);
|
|
}
|
|
selectedHints.push_back(0);
|
|
DistributeHints(selectedHints, totalStones, distTable, hintSetting.junkWeight);
|
|
|
|
while(totalStones != 0){
|
|
totalStones = PlaceHints(selectedHints, distTable);
|
|
if (totalStones != 0){
|
|
DistributeHints(selectedHints, totalStones, distTable, hintSetting.junkWeight, false);
|
|
}
|
|
}
|
|
|
|
//Getting gossip stone locations temporarily sets one location to not be reachable.
|
|
//Call the function one last time to get rid of false positives on locations not
|
|
//being reachable.
|
|
GetAccessibleLocations({});
|
|
}
|
|
|
|
std::vector<RandomizerCheck> FindItemsAndMarkHinted(std::vector<RandomizerGet> items, std::vector<RandomizerCheck> hintChecks, bool alwaysAvalible = false){
|
|
std::vector<RandomizerCheck> locations = {};
|
|
auto ctx = Rando::Context::GetInstance();
|
|
for (uint8_t c = 0; c < items.size(); c++){
|
|
std::vector<RandomizerCheck> found = FilterFromPool(ctx->allLocations, [items, ctx, c](const RandomizerCheck loc) {
|
|
return ctx->GetItemLocation(loc)->GetPlacedRandomizerGet() == items[c];});
|
|
if (found.size() > 0){
|
|
locations.push_back(found[0]);
|
|
}
|
|
if (alwaysAvalible || (!ctx->GetItemLocation(found[0])->IsAHintAccessible() && IsReachableWithout(hintChecks,found[0],true))){
|
|
ctx->GetItemLocation(found[0])->SetHintAccesible();
|
|
}
|
|
}
|
|
return locations;
|
|
}
|
|
|
|
void CreateChildAltarHint() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
std::vector<RandomizerCheck> stoneLocs = {};
|
|
if (ctx->GetOption(RSK_TOT_ALTAR_HINT)) {
|
|
//force marking the rewards as hinted if they are at the end of dungeons as they can be inffered
|
|
stoneLocs = FindItemsAndMarkHinted({RG_KOKIRI_EMERALD, RG_GORON_RUBY, RG_ZORA_SAPPHIRE}, {RC_ALTAR_HINT_CHILD}, ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON));
|
|
}
|
|
ctx->AddHint(RH_ALTAR_CHILD, Hint(RH_ALTAR_CHILD, HINT_TYPE_ALTAR_CHILD, {}, stoneLocs));
|
|
}
|
|
|
|
void CreateAdultAltarHint() {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
std::vector<RandomizerCheck> medallionLocs = {};
|
|
|
|
if (ctx->GetOption(RSK_TOT_ALTAR_HINT)) {
|
|
//force marking the rewards as hinted if they are at the end of dungeons as they can be inffered
|
|
medallionLocs = FindItemsAndMarkHinted({RG_LIGHT_MEDALLION, RG_FOREST_MEDALLION, RG_FIRE_MEDALLION, RG_WATER_MEDALLION, RG_SPIRIT_MEDALLION, RG_SHADOW_MEDALLION},
|
|
{RC_ALTAR_HINT_ADULT}, ctx->GetOption(RSK_SHUFFLE_DUNGEON_REWARDS).Is(RO_DUNGEON_REWARDS_END_OF_DUNGEON));
|
|
}
|
|
ctx->AddHint(RH_ALTAR_ADULT, Hint(RH_ALTAR_ADULT, HINT_TYPE_ALTAR_ADULT, {}, medallionLocs));
|
|
}
|
|
|
|
void CreateStaticHintFromData(RandomizerHint hint, StaticHintInfo staticData){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
Option option = ctx->GetOption(staticData.setting);
|
|
if ((std::holds_alternative<bool>(staticData.condition) && option.Is(std::get<bool>(staticData.condition))) ||
|
|
(std::holds_alternative<uint8_t>(staticData.condition) && option.Is(std::get<uint8_t>(staticData.condition)))){
|
|
std::vector<RandomizerCheck> locations = staticData.targetChecks;
|
|
if (staticData.hintChecks.size() > 0){
|
|
locations = FindItemsAndMarkHinted(staticData.targetItems, staticData.hintChecks);
|
|
}
|
|
ctx->AddHint(hint, Hint(hint, staticData.type, staticData.hintKeys, locations, {}, {}, staticData.yourPocket, staticData.num));
|
|
}
|
|
}
|
|
|
|
void CreateStaticItemHint(RandomizerHint hintKey, std::vector<RandomizerHintTextKey> hintTextKeys,
|
|
std::vector<RandomizerGet> items, std::vector<RandomizerCheck> hintChecks, bool alwaysAvalible = false, bool yourPocket = false) {
|
|
auto ctx = Rando::Context::GetInstance();
|
|
std::vector<RandomizerCheck> locations = FindItemsAndMarkHinted(items, hintChecks, alwaysAvalible);
|
|
ctx->AddHint(hintKey, Hint(hintKey, HINT_TYPE_AREA, hintTextKeys, locations, {}, {}, yourPocket));
|
|
}
|
|
|
|
void CreateGanondorfJoke(){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
ctx->AddHint(RH_GANONDORF_JOKE, Hint(RH_GANONDORF_JOKE, HINT_TYPE_HINT_KEY, {GetRandomGanonJoke()}));
|
|
}
|
|
|
|
void CreateGanondorfHint(){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
if (ctx->GetOption(RSK_GANONDORF_HINT)){
|
|
if (ctx->GetOption(RSK_SHUFFLE_MASTER_SWORD)) {
|
|
CreateStaticItemHint(RH_GANONDORF_HINT, {RHT_GANONDORF_HINT_LA_ONLY, RHT_GANONDORF_HINT_MS_ONLY, RHT_GANONDORF_HINT_LA_AND_MS},
|
|
{RG_LIGHT_ARROWS, RG_MASTER_SWORD}, {RC_GANONDORF_HINT}, false, true);
|
|
} else {
|
|
CreateStaticItemHint(RH_GANONDORF_HINT, {RHT_GANONDORF_HINT_LA_ONLY}, {RG_LIGHT_ARROWS}, {RC_GANONDORF_HINT}, false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CreateAllHints(){
|
|
auto ctx = Rando::Context::GetInstance();
|
|
|
|
CreateChildAltarHint();
|
|
CreateAdultAltarHint();
|
|
CreateGanondorfJoke();
|
|
CreateGanondorfHint();
|
|
for (auto[hint, staticData] : StaticData::staticHintInfoMap){
|
|
CreateStaticHintFromData(hint, staticData);
|
|
}
|
|
|
|
if (ctx->GetOption(RSK_GOSSIP_STONE_HINTS).IsNot(RO_GOSSIP_STONES_NONE)) {
|
|
printf("\x1b[10;10HCreating Hints...");
|
|
CreateStoneHints();
|
|
printf("Done");
|
|
}
|
|
}
|