implement custom item text

This commit is contained in:
gymnast86
2026-05-07 23:17:58 -07:00
parent 870bd6476c
commit 0014be0e82
14 changed files with 1047 additions and 208 deletions
+2
View File
@@ -1521,6 +1521,8 @@ set(DUSK_FILES
# Randomizer files
src/dusk/randomizer/game/flags.cpp
src/dusk/randomizer/game/flags.h
src/dusk/randomizer/game/messages.cpp
src/dusk/randomizer/game/messages.hpp
src/dusk/randomizer/game/stages.cpp
src/dusk/randomizer/game/stages.h
src/dusk/randomizer/game/tools.cpp
+8
View File
@@ -7,6 +7,10 @@
#include "JSystem/JMessage/control.h"
#if TARGET_PC
#include "dusk/randomizer/game/messages.hpp"
#endif
JMessage::TControl::TControl()
: pSequenceProcessor_(NULL),
pRenderingProcessor_(NULL),
@@ -90,6 +94,10 @@ bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const*
JUT_ASSERT(155, pResourceCache_!=NULL);
pMessageText_begin_ = pResourceCache_->getMessageText_messageEntry(pEntry_);
#if TARGET_PC
// Feels kinda hacky to have to hijack this deep into JSystem, but works for now
HandleTextOverrides(this, pProcessor, uMessageGroupID_, uMessageID_);
#endif
pMessageText_current_ = pMessageText_begin_;
oStack_renderingProcessor_.clear();
return true;
+16 -4
View File
@@ -213,12 +213,18 @@ static bool isOutfontKanjiCode(int iCharacter) {
}
static u32 getFontCCColorTable(u8 i_colorNo, u8 i_fukiKind) {
static const u32 colorTable[9] = {
static const u32 colorTable[DUSK_IF_ELSE(12, 9)] = {
0xFFFFFFFF, 0xF07878FF, 0xAADC8CFF, 0xA0B4DCFF, 0xDCDC82FF,
0xB4C8E6FF, 0xC8A0DCFF, 0xFFFFFFFF, 0xDCAA78FF,
#if TARGET_PC
// Extra text colors for randomizer
0x4BBE4BFF, // Dark Green
0x4B96D7FF, // Blue
0xBFBFBFFF, // Silver
#endif
};
if (i_colorNo > 8) {
if (i_colorNo > DUSK_IF_ELSE(11, 8)) {
return 0xFFFFFFFF;
}
@@ -247,12 +253,18 @@ static u32 getFontCCColorTable(u8 i_colorNo, u8 i_fukiKind) {
}
static u32 getFontGCColorTable(u8 i_colorNo, u8 i_fukiKind) {
static const u32 colorTable[9] = {
static const u32 colorTable[DUSK_IF_ELSE(12, 9)] = {
0xFFFFFFFF, 0xF07878FF, 0xAADC8CFF, 0xA0B4DCFF, 0xDCDC82FF,
0xB4C8E6FF, 0xC8A0DCFF, 0xFFFFFFFF, 0xDCAA78FF,
#if TARGET_PC
// Extra text colors for randomizer
0x4BBE4BFF, // Dark Green
0x4B96D7FF, // Blue
0xBFBFBFFF, // Silver
#endif
};
if (i_colorNo > 8) {
if (i_colorNo > DUSK_IF_ELSE(11, 8)) {
return 0xFFFFFFFF;
}
+2 -1
View File
@@ -523,7 +523,8 @@ int dMsgFlow_c::setNormalMsg(mesg_flow_node* i_flowNode_p, fopAc_ac_c* i_speaker
if (flowItemOverrides.contains(key)) {
u8 itemId = verifyProgressiveItem(flowItemOverrides[key]);
msg_no = getItemMessageID(itemId);
execItemGet(itemId);
// Store this itemId so that we can give the item when the textbox closes
g_randomizerState.mFlowMessageItemId = itemId;
}
}
#endif
+14 -4
View File
@@ -697,13 +697,14 @@ u32 dMsgObject_c::getMessageIndex(u32 param_0) {
u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) {
#if TARGET_PC
if (randomizer_IsActive()) {
// Special case for Ilia Memory Reward Text
// Special case for Ilia Memory Reward Text (param_1 is msgId)
// If we're in the sanctuary cutscene where we get the reward, override the text.
// Otherwise we override the text whenever we get the regular horse call
// Otherwise the regular item text for the horse call would be overriden if we find it
if (param_1 == 233 && playerIsInRoomStage(0, "R_SP109") && dComIfGp_getLayerNo() == 9) {
u8 itemId = verifyProgressiveItem(randomizer_getItemAtLocation("Ilia Memory Reward"));
param_1 = getItemMessageID(itemId);
execItemGet(itemId);
// Store this itemId so that we can give the item when the textbox closes
g_randomizerState.mFlowMessageItemId = itemId;
} else {
// Else override the text if we have an override
u32 key = (dMsgObject_getGroupID() << 16) | param_1;
@@ -711,7 +712,8 @@ u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) {
if (flowItemOverrides.contains(key)) {
u8 itemId = verifyProgressiveItem(flowItemOverrides[key]);
param_1 = getItemMessageID(itemId);
execItemGet(itemId);
// Store this itemId so that we can give the item when the textbox closes
g_randomizerState.mFlowMessageItemId = itemId;
}
}
}
@@ -814,6 +816,14 @@ void dMsgObject_c::waitProc() {
}
}
}
#if TARGET_PC
// If we have a randomizer item to give because of a flow message override
// then give it if the textbox has been fully closed.
if (randomizer_IsActive() && g_randomizerState.mFlowMessageItemId != 0 && mpScrnDraw == NULL) {
execItemGet(g_randomizerState.mFlowMessageItemId);
g_randomizerState.mFlowMessageItemId = 0;
}
#endif
}
void dMsgObject_c::openProc() {
+65
View File
@@ -0,0 +1,65 @@
#include "messages.hpp"
#include "JSystem/JMessage/control.h"
#include "d/d_msg_class.h"
#include "randomizer_context.hpp"
#include <format>
// Format certain messages that need to have dynamic info in them
const char* GetFormatedTextOverride(u32 key, const std::string& text) {
// Store formatted message in static buffer so it never goes away.
// This is fine as long as we only ever need to format messages
// for textboxes, but will cause issues if we need to use it for
// other UI elements
static std::array<char, 256> buf;
u32 value{};
char* outIt;
// For item counts, execItemGet hasn't run yet, so add one to the count
switch (key) {
case (0 << 16) | 325: // Group 0, id 325
// Poe Soul get item text
value = dComIfGs_getPohSpiritNum() + 1;
outIt = std::vformat_to(buf.data(), text, std::make_format_args(value));
break;
case (0 << 16) | 335: // Group 0, id 335
// Sky book characters get item text
value = dComIfGs_getAncientDocumentNum() + 1;
outIt = std::vformat_to(buf.data(), text, std::make_format_args(value));
break;
default:
// No override, return original text
return text.data();
}
// Null-terminate
size_t len = std::distance(buf.data(), outIt);
buf[len] = '\0';
// Return overriden text
return buf.data();
}
void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index) {
if (randomizer_IsActive()) {
// Get the entry for this message
auto entry = static_cast<JMSMesgEntry_c*>(pProcessor->getMessageEntry_messageCode(groupID, index));
if (!entry) {
return;
}
// If the message id is >= 5000 then it's part of the stage file's message group
// Otherwise it's part of group 0
auto msgId = entry->message_id.host();
u16 group = 0;
if (msgId >= 5000) {
group = dComIfGp_getStageStagInfo()->mMsgGroup;
}
u32 key = (group << 16) | msgId;
auto& textOverrides = randomizer_GetContext().mTextOverrides;
if (textOverrides.contains(key)) {
control->pMessageText_begin_ = GetFormatedTextOverride(key, textOverrides[key]);
}
}
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
// Forward declaration
namespace JMessage {
struct TProcessor;
struct TControl;
}
void HandleTextOverrides(JMessage::TControl* control, JMessage::TProcessor const* pProcessor, int groupID, int index);
@@ -9,6 +9,7 @@
#include "dusk/randomizer/generator/utility/endian.hpp"
#include "dusk/randomizer/generator/utility/yaml.hpp"
#include "dusk/randomizer/generator/randomizer.hpp"
#include "dusk/randomizer/generator/utility/text.hpp"
#include "SDL3/SDL_filesystem.h"
#include <zlib-ng.h>
@@ -93,7 +94,20 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
out["mFlowPatches"] = this->mFlowPatches;
// Dump text overrides as binary to avoid losing intentional null characters
YAML::Emitter textData;
textData << YAML::BeginMap;
textData << YAML::Key << "mTextOverrides";
textData << YAML::BeginMap;
for (const auto& [key, text] : this->mTextOverrides) {
textData << YAML::Key << key;
textData << YAML::Value << YAML::Binary(reinterpret_cast<const unsigned char*>(text.data()), text.size());
}
textData << YAML::EndMap;
textData << YAML::EndMap;
seedData << YAML::Dump(out);
seedData << '\n' << textData.c_str();
seedData.close();
return std::nullopt;
@@ -227,6 +241,14 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
this->mFlowPatches[key] = value;
}
// Text Overrides
for (const auto& textNode: in["mTextOverrides"]) {
auto key = textNode.first.as<u32>();
auto binary = textNode.second.as<YAML::Binary>();
std::string text(reinterpret_cast<const char*>(binary.data()), binary.size());
this->mTextOverrides[key] = std::move(text);
}
DuskLog.debug("Loaded Randomizer Seed {}", this->mHash);
return std::nullopt;
@@ -1055,6 +1077,20 @@ RandomizerContext WriteSeedData(const std::unique_ptr<randomizer::logic::world::
}
}
// Text Overrides
auto textOverrides = LoadYAML(RANDO_DATA_PATH "text/text_overrides.yaml");
for (const auto& overrideNode : textOverrides) {
const auto& name = overrideNode["Name"].as<std::string>();
// TODO: Handle multiple languages
auto language = randomizer::Text::ENGLISH;
auto text = randomizer::getTextStr(name);
u8 group = overrideNode["Group"].as<u8>();
u16 messageId = overrideNode["Message Id"].as<u16>();
u32 key = (group << 16) | messageId;
randomizer::applyMessageCodes(text);
randoData.mTextOverrides[key] = text;
}
return std::move(randoData);
}
@@ -46,6 +46,12 @@ public:
std::unordered_map<u32, std::unordered_map<u32, std::list<std::array<u8, 30>>>> mActorAdditions{};
std::unordered_map<u32, u64> mFlowPatches{};
// struct TextOverride {
// std::array<u8, 16> mAttributes{};
// std::string mText{};
// };
std::unordered_map<u32, std::string> mTextOverrides{};
std::optional<std::string> WriteToFile();
std::optional<std::string> LoadFromHash(const std::string& hash);
std::string GetSeedDataPath() const;
@@ -133,6 +139,12 @@ public:
u8 mTimeChange{};
u8 mEventItemQueue[EVENT_ITEM_QUEUE_SIZE];
bool mRoomReloadingState{false};
// Used to store an item id for a flow message override so that we can give the item
// once the textbox is closed instead of when the message appears. This lines up
// more naturally with how the timing of how the game normally gives items and affects
// things like the sound of the rupee counter going up.
u8 mFlowMessageItemId{0};
};
extern RandomizerState g_randomizerState;
@@ -106,6 +106,10 @@
Importance: Junk
Id: 0x18
#- Name: Water Bombs 3
# Importance: Junk
# Id: 0x19
- Name: Bomblings 5
Importance: Junk
Id: 0x1A
@@ -0,0 +1,325 @@
Shadow Crystal Get Item Text:
Standard:
Text: |-
<slow>You got the <red>Shadow Crystal<white>!
This is a dark manifestation
of <red>Zant's<white> power that allows
you to transform at will!
Restored Dominion Rod Text:
Standard:
Text: |-
<fast>Power has been restored to
the <red>Dominion Rod<white>! Now it can
be used to imbue statues
with life in the present!
Forest Temple Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<green>Forest Temple<white>!
Goron Mines Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for
<red>Goron Mines<white>!
Lakebed Temple Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<blue>Lakebed Temple<white>!
Arbiters Grounds Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for
<orange>Arbiter's Grounds<white>!
Snowpeak Ruins Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for
<light blue>Snowpeak Ruins<white>!
Temple of Time Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<dark green>Temple of Time<white>!
City in the Sky Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<yellow>City in the Sky<white>!
Palace of Twilight Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<purple>Palace of Twilight<white>!
Hyrule Castle Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for
<silver>Hyrule Castle<white>!
Bulblin Camp Small Key Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Small Key<white> for the
<orange>Bulblin Camp<white>!
Forest Temple Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for the
<green>Forest Temple<white>!
Lakebed Temple Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for the
<blue>Lakebed Temple<white>!
Arbiters Grounds Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for
<orange>Arbiter's Grounds<white>!
Temple of Time Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for the
<dark green>Temple of Time<white>!
City in the Sky Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for the
<yellow>City in the Sky<white>!
Palace of Twilight Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for the
<purple>Palace of Twilight<white>!
Hyrule Castle Big Key Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Big Key<white> for
<silver>Hyrule Castle<white>!
Forest Temple Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for the
<green>Forest Temple<white>!
Goron Mines Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for
<red>Goron Mines<white>!
Lakebed Temple Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for the
<blue>Lakebed Temple<white>!
Mirror Shard 2 Get item Text:
Standard:
Text: |-
<fast>You got the second shard of
the <red>Mirror of Twilight<white>! It
has a beautiful shine to it
and feels slightly <light blue>cold<white>...
Mirror Shard 3 Get item Text:
Standard:
Text: |-
<fast>You got the third shard of
the <red>Mirror of Twilight<white>! It
is covered in dirt and
<dark green>webs<white>...
Mirror Shard 4 Get item Text:
Standard:
Text: |-
<fast>You got the final shard of
the <red>Mirror of Twilight<white>! It
feels lighter than <yellow>air<white>...
Arbiters Grounds Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for
<orange>Arbiter's Grounds<white>!
Snowpeak Ruins Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for
<light blue>Snowpeak Ruins<white>!
Temple of Time Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for the
<dark green>Temple of Time<white>!
City in the Sky Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for the
<yellow>City in the Sky<white>!
Palace of Twilight Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for the
<purple>Palace of Twilight<white>!
Hyrule Castle Compass Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Compass<white> for
<silver>Hyrule Castle<white>!
<fast>
#
Forest Temple Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for the
<green>Forest Temple<white>!
Goron Mines Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for
<red>Goron Mines<white>!
Lakebed Temple Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for the
<blue>Lakebed Temple<white>!
Snowpeak Ruins Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for
<light blue>Snowpeak Ruins<white>!
Arbiters Grounds Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for
<orange>Arbiter's Grounds<white>!
Temple of Time Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for the
<dark green>Temple of Time<white>!
City in the Sky Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for the
<yellow>City in the Sky<white>!
Palace of Twilight Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for the
<purple>Palace of Twilight<white>!
Hyrule Castle Dungeon Map Get Item Text:
Standard:
Text: |-
<fast>You got the <red>Dungeon Map<white> for
<silver>Hyrule Castle<white>!
<fast>
Fused Shadow 1 Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Fused Shadow<white>!
It seems to have some <green>moss<white>
growing on it...
Fused Shadow 2 Get Item Text:
Standard:
Text: |-
<fast>You got the second <red>Fused
Shadow<white>! It feels <red>warm<white> to
the touch...
Fused Shadow 3 Get Item Text:
Standard:
Text: |-
<fast>You got the final <red>Fused
Shadow<white>! It feels <blue>wet<white> and
smells like fish...
Mirror Shard 1 Get Item Text:
Standard:
Text: |-
<fast>You got the first shard of
the <red>Mirror of Twilight<white>! It
is covered in <orange>sand<white>...
Poe Soul Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Poe's Soul<white>!
You've collected <red>{}<white> so far.
Ending Blow Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Ending Blow<white>!
Shield Attack Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Shield Attack<white>!
Back Slice Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Back Slice<white>!
Helm Splitter Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Helm Splitter<white>!
Mortal Draw Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Mortal Draw<white>!
Jump Strike Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Jump Strike<white>!
Great Spin Get Item Text:
Standard:
Text: |-
<fast>You learned the <red>Great Spin<white>!
Partially Filled Sky Book Get Item Text:
Standard:
Text: |-
<fast>You got a <red>Sky Character<white>!
You've collected <red>{}<white> so far.
@@ -0,0 +1,223 @@
#- Name: Foolish Get Item Text
# Group: 0
# Message Id: 120
#
#- Name: Ordon Spring Portal Get Item Text
# Group: 0
# Message Id: 121
#
#- Name: South Faron Portal Get Item Text
# Group: 0
# Message Id: 122
- Name: Shadow Crystal Get Item Text
Group: 0
Message Id: 151
- Name: Restored Dominion Rod Text
Group: 0
Message Id: 177
- Name: Forest Temple Small Key Get Item Text
Group: 0
Message Id: 234
- Name: Goron Mines Small Key Get Item Text
Group: 0
Message Id: 235
- Name: Lakebed Temple Small Key Get Item Text
Group: 0
Message Id: 236
- Name: Arbiters Grounds Small Key Get Item Text
Group: 0
Message Id: 237
- Name: Snowpeak Ruins Small Key Get Item Text
Group: 0
Message Id: 238
- Name: Temple of Time Small Key Get Item Text
Group: 0
Message Id: 239
- Name: City in the Sky Small Key Get Item Text
Group: 0
Message Id: 240
- Name: Palace of Twilight Small Key Get Item Text
Group: 0
Message Id: 241
- Name: Hyrule Castle Small Key Get Item Text
Group: 0
Message Id: 242
- Name: Bulblin Camp Small Key Get Item Text
Group: 0
Message Id: 243
- Name: Forest Temple Big Key Get Item Text
Group: 0
Message Id: 247
- Name: Lakebed Temple Big Key Get Item Text
Group: 0
Message Id: 248
- Name: Arbiters Grounds Big Key Get Item Text
Group: 0
Message Id: 249
- Name: Temple of Time Big Key Get Item Text
Group: 0
Message Id: 250
- Name: City in the Sky Big Key Get Item Text
Group: 0
Message Id: 251
- Name: Palace of Twilight Big Key Get Item Text
Group: 0
Message Id: 252
- Name: Hyrule Castle Big Key Get Item Text
Group: 0
Message Id: 253
- Name: Forest Temple Compass Get Item Text
Group: 0
Message Id: 254
- Name: Goron Mines Compass Get Item Text
Group: 0
Message Id: 255
- Name: Lakebed Temple Compass Get Item Text
Group: 0
Message Id: 256
- Name: Mirror Shard 2 Get item Text
Group: 0
Message Id: 266
- Name: Mirror Shard 3 Get item Text
Group: 0
Message Id: 267
- Name: Mirror Shard 4 Get item Text
Group: 0
Message Id: 268
- Name: Arbiters Grounds Compass Get Item Text
Group: 0
Message Id: 269
- Name: Snowpeak Ruins Compass Get Item Text
Group: 0
Message Id: 270
- Name: Temple of Time Compass Get Item Text
Group: 0
Message Id: 271
- Name: City in the Sky Compass Get Item Text
Group: 0
Message Id: 272
- Name: Palace of Twilight Compass Get Item Text
Group: 0
Message Id: 273
- Name: Hyrule Castle Compass Get Item Text
Group: 0
Message Id: 274
- Name: Forest Temple Dungeon Map Get Item Text
Group: 0
Message Id: 283
- Name: Goron Mines Dungeon Map Get Item Text
Group: 0
Message Id: 284
- Name: Lakebed Temple Dungeon Map Get Item Text
Group: 0
Message Id: 285
- Name: Arbiters Grounds Dungeon Map Get Item Text
Group: 0
Message Id: 286
- Name: Snowpeak Ruins Dungeon Map Get Item Text
Group: 0
Message Id: 287
- Name: Temple of Time Dungeon Map Get Item Text
Group: 0
Message Id: 288
- Name: City in the Sky Dungeon Map Get Item Text
Group: 0
Message Id: 289
- Name: Palace of Twilight Dungeon Map Get Item Text
Group: 0
Message Id: 290
- Name: Hyrule Castle Dungeon Map Get Item Text
Group: 0
Message Id: 291
- Name: Fused Shadow 1 Get Item Text
Group: 0
Message Id: 317
- Name: Fused Shadow 2 Get Item Text
Group: 0
Message Id: 318
- Name: Fused Shadow 3 Get Item Text
Group: 0
Message Id: 319
- Name: Mirror Shard 1 Get Item Text
Group: 0
Message Id: 320
- Name: Poe Soul Get Item Text
Group: 0
Message Id: 325
- Name: Ending Blow Get Item Text
Group: 0
Message Id: 326
- Name: Shield Attack Get Item Text
Group: 0
Message Id: 327
- Name: Back Slice Get Item Text
Group: 0
Message Id: 328
- Name: Helm Splitter Get Item Text
Group: 0
Message Id: 329
- Name: Mortal Draw Get Item Text
Group: 0
Message Id: 330
- Name: Jump Strike Get Item Text
Group: 0
Message Id: 331
- Name: Great Spin Get Item Text
Group: 0
Message Id: 332
- Name: Partially Filled Sky Book Get Item Text
Group: 0
Message Id: 335
+268 -155
View File
@@ -1,178 +1,291 @@
// #include "../utility/text.hpp"
// #include "../utility/string.hpp"
// #include <filetypes/util/msbtMacros.hpp"
// #include <command/Log.hpp"
#include "text.hpp"
// #include <unordered_map>
#include <unordered_map>
#include <filesystem>
#include <fstream>
// namespace Text {
#include "yaml.hpp"
// std::array<std::string, 3> supported_languages = {"English", "Spanish", "French"};
namespace randomizer {
// static std::unordered_map<Text::Color, std::u16string> nameToColor = {
// {Text::Color::NONE, TEXT_COLOR_DEFAULT},
// {Text::Color::RED, TEXT_COLOR_RED},
// {Text::Color::GREEN, TEXT_COLOR_GREEN},
// {Text::Color::BLUE, TEXT_COLOR_BLUE},
// {Text::Color::YELLOW, TEXT_COLOR_YELLOW},
// {Text::Color::CYAN, TEXT_COLOR_CYAN},
// {Text::Color::MAGENTA, TEXT_COLOR_MAGENTA},
// {Text::Color::GRAY, TEXT_COLOR_GRAY},
// {Text::Color::ORANGE, TEXT_COLOR_ORANGE},
// };
// std::array<std::string, 3> supported_languages = {"English", "Spanish", "French"};
//
// static std::unordered_map<Text::Color, std::u16string> nameToColor = {
// {Text::Color::NONE, TEXT_COLOR_DEFAULT},
// {Text::Color::RED, TEXT_COLOR_RED},
// {Text::Color::GREEN, TEXT_COLOR_GREEN},
// {Text::Color::BLUE, TEXT_COLOR_BLUE},
// {Text::Color::YELLOW, TEXT_COLOR_YELLOW},
// {Text::Color::CYAN, TEXT_COLOR_CYAN},
// {Text::Color::MAGENTA, TEXT_COLOR_MAGENTA},
// {Text::Color::GRAY, TEXT_COLOR_GRAY},
// {Text::Color::ORANGE, TEXT_COLOR_ORANGE},
// };
//
// std::u16string apply_name_color(std::u16string str, const Color& color)
// {
// // Return the raw text (bars included)
// if (color == Color::RAW)
// {
// return str;
// }
// // If there are no '|'s then just return with the color surrounding the whole string
// if (str.find('|') == std::string::npos)
// {
// auto textColor = nameToColor[color];
// return textColor + str + TEXT_COLOR_DEFAULT;
// }
//
// // Alternate between the text color and default incase there are multiple
// // pairs of bars
// auto textColor = nameToColor[color];
// bool insertColor = false;
// for (size_t pos = 0; pos < str.length(); pos++)
// {
// if (str[pos] == '|')
// {
// insertColor = !insertColor;
// str.erase(pos, 1);
// str.insert(pos, insertColor ? textColor : TEXT_COLOR_DEFAULT);
// }
// }
//
// return str;
// }
//
// std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len) {
// size_t index_in_str = 0;
// std::u16string wordwrapped_str;
// std::u16string current_word;
// size_t curr_word_len = 0;
// size_t len_curr_line = 0;
//
// while (index_in_str < string.length()) { //length is weird because its utf-16
// char16_t character = string[index_in_str];
//
// if (character == u'\x0E') { //need to parse the commands, only implementing a few necessary ones for now (will break with other commands)
// std::u16string substr;
// size_t code_len = 0;
// if (string[index_in_str + 1] == u'\x00') {
// if (string[index_in_str + 2] == u'\x03') { //color command
// if (string[index_in_str + 4] == u'\xFFFF') { //text color white, weird length
// code_len = 10;
// }
// else {
// code_len = 5;
// }
// }
// }
// else if (string[index_in_str + 1] == u'\x01') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x02') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x03') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x04') { //all implemented commands in this group have length 4. Only used for Ho Ho sound
// code_len = 4;
// }
//
// substr = string.substr(index_in_str, code_len);
// current_word += substr;
// index_in_str += code_len;
// }
// else if (character == u'\n') {
// wordwrapped_str += current_word;
// wordwrapped_str += character;
// len_curr_line = 0;
// current_word = u"";
// curr_word_len = 0;
// index_in_str += 1;
// }
// else if (character == u' ') {
// wordwrapped_str += current_word;
// wordwrapped_str += character;
// len_curr_line += curr_word_len + 1;
// current_word = u"";
// curr_word_len = 0;
// index_in_str += 1;
// }
// else {
// current_word += character;
// curr_word_len += 1;
// index_in_str += 1;
//
// if (len_curr_line + curr_word_len > max_line_len) {
// wordwrapped_str += u'\n';
// len_curr_line = 0;
//
// if (curr_word_len > max_line_len) {
// wordwrapped_str += current_word + u'\n';
// current_word = u"";
// }
// }
// }
// }
// wordwrapped_str += current_word;
//
// return wordwrapped_str;
// }
//
// std::string pad_str_4_lines(const std::string& string)
// {
// std::vector<std::string> lines = randomizer::utility::str::Split(string, '\n');
//
// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4;
// for (unsigned int i = 0; i < padding_lines_needed; i++)
// {
// lines.push_back("");
// }
//
// return randomizer::utility::str::Merge(lines, '\n');
// }
//
// std::u16string pad_str_4_lines(const std::u16string& string)
// {
// std::vector<std::u16string> lines = randomizer::utility::str::Split(string, u'\n');
//
// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4;
// for (unsigned int i = 0; i < padding_lines_needed; i++)
// {
// lines.push_back(u"");
// }
//
// return randomizer::utility::str::erge(lines, u'\n');
// }
// std::u16string apply_name_color(std::u16string str, const Color& color)
// {
// // Return the raw text (bars included)
// if (color == Color::RAW)
// {
// return str;
// }
// // If there are no '|'s then just return with the color surrounding the whole string
// if (str.find('|') == std::string::npos)
// {
// auto textColor = nameToColor[color];
// return textColor + str + TEXT_COLOR_DEFAULT;
// }
// // Alternate between the text color and default incase there are multiple
// // pairs of bars
// auto textColor = nameToColor[color];
// bool insertColor = false;
// for (size_t pos = 0; pos < str.length(); pos++)
// {
// if (str[pos] == '|')
// {
// insertColor = !insertColor;
// str.erase(pos, 1);
// str.insert(pos, insertColor ? textColor : TEXT_COLOR_DEFAULT);
// }
// }
Text::Type string_to_type(const std::string& str) {
std::unordered_map<std::string, Text::Type> strToType = {
{"Standard", Text::Type::STANDARD},
{"Pretty", Text::Type::PRETTY},
{"Cryptic", Text::Type::CRYPTIC},
};
// return str;
// }
if (strToType.contains(str))
{
return strToType.at(str);
}
// std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len) {
// size_t index_in_str = 0;
// std::u16string wordwrapped_str;
// std::u16string current_word;
// size_t curr_word_len = 0;
// size_t len_curr_line = 0;
throw std::runtime_error("Text type \"" + str + "\" is not recognized.");
}
// while (index_in_str < string.length()) { //length is weird because its utf-16
// char16_t character = string[index_in_str];
Text::Language string_to_language(const std::string& str) {
std::unordered_map<std::string, Text::Language> strToLanguage = {
{"english", Text::Language::ENGLISH},
};
// if (character == u'\x0E') { //need to parse the commands, only implementing a few necessary ones for now (will break with other commands)
// std::u16string substr;
// size_t code_len = 0;
// if (string[index_in_str + 1] == u'\x00') {
// if (string[index_in_str + 2] == u'\x03') { //color command
// if (string[index_in_str + 4] == u'\xFFFF') { //text color white, weird length
// code_len = 10;
// }
// else {
// code_len = 5;
// }
// }
// }
// else if (string[index_in_str + 1] == u'\x01') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x02') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x03') { //all implemented commands in this group have length 4
// code_len = 4;
// }
// else if (string[index_in_str + 1] == u'\x04') { //all implemented commands in this group have length 4. Only used for Ho Ho sound
// code_len = 4;
// }
if (strToLanguage.contains(str))
{
return strToLanguage.at(str);
}
// substr = string.substr(index_in_str, code_len);
// current_word += substr;
// index_in_str += code_len;
// }
// else if (character == u'\n') {
// wordwrapped_str += current_word;
// wordwrapped_str += character;
// len_curr_line = 0;
// current_word = u"";
// curr_word_len = 0;
// index_in_str += 1;
// }
// else if (character == u' ') {
// wordwrapped_str += current_word;
// wordwrapped_str += character;
// len_curr_line += curr_word_len + 1;
// current_word = u"";
// curr_word_len = 0;
// index_in_str += 1;
// }
// else {
// current_word += character;
// curr_word_len += 1;
// index_in_str += 1;
throw std::runtime_error("Language \"" + str + "\" is not recognized.");
}
// if (len_curr_line + curr_word_len > max_line_len) {
// wordwrapped_str += u'\n';
// len_curr_line = 0;
Text::Gender string_to_gender(const std::string& str)
{
std::unordered_map<std::string, Text::Gender> strToGender = {
{"Masculine", Text::Gender::MASCULINE},
{"Feminine", Text::Gender::FEMININE}
};
// if (curr_word_len > max_line_len) {
// wordwrapped_str += current_word + u'\n';
// current_word = u"";
// }
// }
// }
// }
// wordwrapped_str += current_word;
if (strToGender.contains(str))
{
return strToGender.at(str);
}
// return wordwrapped_str;
// }
return Text::Gender::NUETRAL;
}
// std::string pad_str_4_lines(const std::string& string)
// {
// std::vector<std::string> lines = randomizer::utility::str::Split(string, '\n');
Text::Plurality string_to_plurality(const std::string& str)
{
if (str == "Plural") return Text::Plurality::PLURAL;
return Text::Plurality::SINGULAR;
}
// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4;
// for (unsigned int i = 0; i < padding_lines_needed; i++)
// {
// lines.push_back("");
// }
static void LoadTextData(TextDatabase& tb) {
std::string dataPath = RANDO_DATA_PATH "text/languages";
for (const auto& entry : std::filesystem::directory_iterator(dataPath)) {
if (entry.is_regular_file() && entry.path().extension() == ".yaml") {
auto language = string_to_language(entry.path().stem().string());
auto textData = LoadYAML(entry.path());
for (const auto& textNode : textData) {
const auto& name = textNode.first.as<std::string>();
for (const auto& typeNode : textNode.second) {
auto type = string_to_type(typeNode.first.as<std::string>());
auto typeData = typeNode.second;
const auto& text = typeData["Text"].as<std::string>();
tb[name][type].mtext[language] = text;
if (typeData["Gender"]) {
tb[name][type].mGender[language] = string_to_gender(typeData["Gender"].as<std::string>());
}
if (typeData["Plurality"]) {
tb[name][type].mPlurality[language] = string_to_plurality(typeData["Plurality"].as<std::string>());
}
}
}
}
}
}
// return randomizer::utility::str::Merge(lines, '\n');
// }
const TextDatabase& getTextDatabase() {
static TextDatabase tb{};
// std::u16string pad_str_4_lines(const std::u16string& string)
// {
// std::vector<std::u16string> lines = randomizer::utility::str::Split(string, u'\n');
// If database is empty, load it up
if (tb.empty()) {
LoadTextData(tb);
}
// unsigned int padding_lines_needed = (4 - lines.size() % 4) % 4;
// for (unsigned int i = 0; i < padding_lines_needed; i++)
// {
// lines.push_back(u"");
// }
return tb;
}
// return randomizer::utility::str::erge(lines, u'\n');
// }
const Text& getTextObject(const std::string& name, Text::Type type /*= Text::STANDARD*/)
{
const auto& tb = getTextDatabase();
if (!tb.contains(name)) {
throw std::runtime_error("Text name \"" + name + "\" is not recognized.");
}
return tb.at(name).at(type);
}
// Gender string_to_gender(const std::string& str)
// {
// std::unordered_map<std::string, Gender> strToGender = {
// {"Male", Gender::MALE},
// {"Female", Gender::FEMALE}
// };
const std::string& getTextStr(const std::string& name,
Text::Type type /*= Text::STANDARD*/,
Text::Language language /*= Text::ENGLISH*/)
{
const auto& tb = getTextDatabase();
if (!tb.contains(name)) {
throw std::runtime_error("Text name \"" + name + "\" is not recognized.");
}
return tb.at(name).at(type).mtext.at(language);
}
// if (strToGender.contains(str))
// {
// return strToGender.at(str);
// }
void applyMessageCodes(std::string& str) {
using namespace std::string_literals;
const static std::unordered_map<std::string, std::string> messageCodes = {
{"<fast>", "\x1A\x05\x00\x00\x01"s },
{"<slow>", "\x1A\x05\x00\x00\x02"s },
{"<white>", "\x1A\x06\xFF\x00\x00\x00"s},
{"<red>", "\x1A\x06\xFF\x00\x00\x01"s},
{"<green>", "\x1A\x06\xFF\x00\x00\x02"s},
{"<light blue>", "\x1A\x06\xFF\x00\x00\x03"s},
{"<yellow>", "\x1A\x06\xFF\x00\x00\x04"s},
{"<purple>", "\x1A\x06\xFF\x00\x00\x06"s},
{"<orange>", "\x1A\x06\xFF\x00\x00\x08"s},
// custom colors
{"<dark green>", "\x1A\x06\xFF\x00\x00\x09"s},
{"<blue>", "\x1A\x06\xFF\x00\x00\x0A"s},
{"<silver>", "\x1A\x06\xFF\x00\x00\x0B"s},
};
// return Gender::NONE;
// }
// Plurality string_to_plurality(const std::string& str)
// {
// if (str == "Plural") return Plurality::PLURAL;
// return Plurality::SINGULAR;
// }
// }; // namespace Text
for (const auto& [code, replacement] : messageCodes) {
size_t pos = 0;
while ((pos = str.find(code, pos)) != std::string::npos) {
str.replace(pos, code.length(), replacement);
pos += replacement.length();
}
}
}
}; // namespace Text
+63 -44
View File
@@ -2,58 +2,77 @@
#include <string>
#include <array>
#include <map>
#include <unordered_map>
namespace Text
{
enum struct Type
{
STANDARD = 0,
PRETTY,
CRYPTIC,
namespace randomizer {
class Text {
public:
enum Language {
ENGLISH = 0,
LANGUAGE_MAX
};
enum Type
{
STANDARD = 0,
PRETTY,
CRYPTIC,
TYPE_MAX
};
enum Color
{
RAW = 0,
NONE,
RED,
GREEN,
BLUE,
YELLOW,
CYAN,
MAGENTA,
GRAY,
ORANGE,
};
enum Gender
{
NUETRAL = 0,
MASCULINE,
FEMININE,
GENDER_MAX,
};
enum Plurality
{
SINGULAR = 0,
PLURAL,
PLURALITY_MAX,
};
std::array<std::string, LANGUAGE_MAX> mtext{};
std::array<Gender, LANGUAGE_MAX> mGender{};
std::array<Plurality, LANGUAGE_MAX> mPlurality{};
};
enum struct Color
{
RAW = 0,
NONE,
RED,
GREEN,
BLUE,
YELLOW,
CYAN,
MAGENTA,
GRAY,
ORANGE,
inline constexpr std::array supported_languages = {
Text::ENGLISH,
};
enum struct Gender
{
NONE = 0,
MALE,
FEMALE,
};
// std::u16string apply_name_color(std::u16string str, const Color& color);
// std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len); //IMPROVEMENT: use font data to do this "properly"
// std::string pad_str_4_lines(const std::string& string);
// std::u16string pad_str_4_lines(const std::u16string& string);
enum struct Plurality
{
SINGULAR,
PLURAL,
};
Text::Gender string_to_gender(const std::string& str);
Text::Plurality string_to_plurality(const std::string& str);
struct Translation
{
std::map<Text::Type, std::string> types;
Gender gender;
Plurality plurality;
};
// Retrieval of Text objects keyed by name and type (standard, pretty, criptic)
using TextDatabase = std::unordered_map<std::string, std::array<Text, Text::TYPE_MAX>>;
extern std::array<std::string, 3> supported_languages;
const TextDatabase& getTextDatabase();
std::u16string apply_name_color(std::u16string str, const Color& color);
std::u16string word_wrap_string(const std::u16string& string, const size_t& max_line_len); //IMPROVEMENT: use font data to do this "properly"
std::string pad_str_4_lines(const std::string& string);
std::u16string pad_str_4_lines(const std::u16string& string);
const std::string& getTextStr(const std::string& name, Text::Type type = Text::STANDARD, Text::Language language = Text::ENGLISH);
Gender string_to_gender(const std::string& str);
Plurality string_to_plurality(const std::string& str);
// Replaces the message codes in the string with the ingame hex equivalents
void applyMessageCodes(std::string&);
}; // namespace Text