add MessageEditor

This commit is contained in:
scawful
2024-07-24 00:01:21 -04:00
parent e7c5cf59a6
commit a9ee33bcd9
6 changed files with 1269 additions and 3 deletions

View File

@@ -170,6 +170,11 @@ void MasterEditor::ManageActiveEditors() {
active_editors_.push_back(&assembly_editor_);
ImGui::CloseCurrentPopup();
}
if (ImGui::MenuItem("Message", nullptr, false,
!IsEditorActive(&message_editor_, active_editors_))) {
active_editors_.push_back(&message_editor_);
ImGui::CloseCurrentPopup();
}
if (ImGui::MenuItem("Settings", nullptr, false,
!IsEditorActive(&settings_editor_, active_editors_))) {
active_editors_.push_back(&settings_editor_);
@@ -257,6 +262,13 @@ void MasterEditor::ManageActiveEditors() {
ImGui::EndTabItem();
}
break;
case EditorType::kMessage:
if (ImGui::BeginTabItem("Message", &open)) {
current_editor_ = &message_editor_;
status_ = message_editor_.Update();
ImGui::EndTabItem();
}
break;
default:
break;
}

View File

@@ -19,6 +19,7 @@
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/message/message_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/settings_editor.h"
@@ -123,6 +124,7 @@ class MasterEditor : public SharedRom,
ScreenEditor screen_editor_;
SpriteEditor sprite_editor_;
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
ImVector<int> active_tabs_;

View File

@@ -0,0 +1,678 @@
#include "message_editor.h"
#include <absl/status/status.h>
#include <absl/strings/str_format.h>
#include <absl/strings/str_replace.h>
#include <absl/strings/str_split.h>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "app/core/common.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using ImGui::Begin;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::End;
using ImGui::EndChild;
using ImGui::EndTable;
using ImGui::InputText;
using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TextWrapped;
using ImGui::TreeNode;
static ParsedElement FindMatchingElement(string str) {
std::smatch match;
for (auto& textElement : TextCommands) {
match = textElement.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
} else {
return ParsedElement(textElement, 0);
}
}
}
match = DictionaryElement.MatchMe(str);
if (match.size() > 0) {
return ParsedElement(DictionaryElement,
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
}
return ParsedElement();
}
static string ReplaceAllDictionaryWords(string str) {
string temp = str;
for (const auto& entry : AllDictionaries) {
if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
}
}
return temp;
}
static std::vector<uint8_t> ParseMessageToData(string str) {
std::vector<uint8_t> bytes;
string tempString = str;
int pos = 0;
while (pos < tempString.size()) {
// Get next text fragment.
if (tempString[pos] == '[') {
int next = tempString.find(']', pos);
if (next == -1) {
break;
}
ParsedElement parsedElement =
FindMatchingElement(tempString.substr(pos, next - pos + 1));
if (!parsedElement.Active) {
break; // TODO: handle badness.
// } else if (parsedElement.Parent == DictionaryElement) {
// bytes.push_back(parsedElement.Value);
} else {
bytes.push_back(parsedElement.Parent.ID);
if (parsedElement.Parent.HasArgument) {
bytes.push_back(parsedElement.Value);
}
}
pos = next + 1;
continue;
} else {
uint8_t bb = MessageEditor::FindMatchingCharacter(tempString[pos++]);
if (bb != 0xFF) {
// TODO: handle badness.
bytes.push_back(bb);
}
}
}
return bytes;
}
absl::Status MessageEditor::Update() {
if (rom()->is_loaded() && !data_loaded_) {
RETURN_IF_ERROR(rom()->LoadFontGraphicsData())
RETURN_IF_ERROR(Initialize());
data_loaded_ = true;
}
if (BeginTable("##MessageEditor", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
TableSetupColumn("##ListOfMessages");
TableSetupColumn("##MessageEditorColumn2");
TableNextColumn();
DrawMessageList();
TableNextColumn();
DrawCurrentMessage();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
for (const auto& message : ListOfTexts) {
TextWrapped("%s", absl::StrCat("Message ", message.RawString).c_str());
SameLine();
if (Button(absl::StrCat("Message ", message.ID).c_str())) {
SelectMessageID(message.ID);
}
}
}
void MessageEditor::DrawCurrentMessage() {
TextWrapped("%s", absl::StrCat("Message ", CurrentMessage.RawString).c_str());
SameLine();
if (Button(absl::StrCat("Message ", CurrentMessage.ID).c_str())) {
SelectMessageID(CurrentMessage.ID);
}
Separator();
ImGui::BeginChild("MessageEditorCanvas", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
Separator();
ImGui::BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
current_font_gfx16_canvas_.DrawBackground();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
Separator();
}
absl::Status MessageEditor::Initialize() {
for (int i = 0; i < 100; i++) {
widthArray[i] = rom()->data()[kCharactersWidth + i];
}
previewColors.AddColor(0x7FFF); // White
previewColors.AddColor(0x7C00); // Red
previewColors.AddColor(0x03E0); // Green
previewColors.AddColor(0x001F); // Blue
fontgfx16Ptr = rom()->font_gfx_data();
// 4bpp
font_gfx_bitmap_.Create(128, 128, 64, gfx::kFormat4bppIndexed, fontgfx16Ptr);
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(128, 128, 64, fontgfx16Ptr,
font_gfx_bitmap_, previewColors))
currentfontgfx16Ptr.reserve(172 * 4096);
for (int i = 0; i < 172 * 4096; i++) {
currentfontgfx16Ptr.push_back(0);
}
// 8bpp
current_font_gfx16_bitmap_.Create(172, 4096, 172, gfx::kFormat8bppIndexed,
currentfontgfx16Ptr);
RETURN_IF_ERROR(
rom()->CreateAndRenderBitmap(172, 4096, 172, currentfontgfx16Ptr,
current_font_gfx16_bitmap_, previewColors))
// auto previewColors = new Color[]{
// Color.DimGray,
// Color.DarkBlue,
// Color.White,
// Color.DarkOrange,
// };
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
for (int i = 0; i < previewColors.size(); i++) {
*color_palette.mutable_color(i) = previewColors[i];
}
*font_gfx_bitmap_.mutable_palette() = color_palette;
BuildDictionaryEntries();
ReadAllTextData();
// foreach (MessageData messageData in
// ListOfTexts) {
// DisplayedMessages.push_back(messageData);
// }
// textListbox.BeginUpdate();
// textListbox.DataSource =
// DisplayedMessages;
// textListbox.EndUpdate();
// textListbox.DisplayMember = "Text";
// pictureBox2.Refresh();
// SelectedTileID.Text =
// selected_tile.ToString("X2");
// SelectedTileASCII.Text =
// ParseTextDataByte((uint8_t)selected_tile);
// CreateFontGfxData(rom()->data());
return absl::OkStatus();
}
void MessageEditor::BuildDictionaryEntries() {
for (int i = 0; i < 97; i++) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
int address = core::SnesToPc(
0x0E0000 + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
0x0E0000 +
(rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom()->data()[address++];
bytes.push_back(uint8_tDictionary);
stringBuilder << ParseTextDataByte(uint8_tDictionary);
}
// AllDictionaries[i] = DictionaryEntry{(uint8_t)i, stringBuilder.str()};
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
}
// AllDictionaries.OrderByDescending(dictionary = > dictionary.Length);
AllDictionaries[0].Length = 0;
}
void MessageEditor::ReadAllTextData() {
int messageID = 0;
uint8_t value;
int pos = kTextData;
std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed;
std::string current_message_raw;
std::string current_message_parsed;
TextElement textElement;
while (true) {
value = rom()->data()[pos++];
if (value == MESSAGETERMINATOR) {
auto message =
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw,
current_message_parsed, temp_bytes_parsed);
ListOfTexts.push_back(message);
temp_bytes_raw.clear();
temp_bytes_parsed.clear();
current_message_raw.clear();
current_message_parsed.clear();
continue;
} else if (value == 0xFF) {
break;
}
temp_bytes_raw.push_back(value);
// Check for command.
textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
temp_bytes_parsed.push_back(value);
if (textElement.HasArgument) {
value = rom()->data()[pos++];
temp_bytes_raw.push_back(value);
temp_bytes_parsed.push_back(value);
}
current_message_raw.append(textElement.GetParameterizedToken(value));
current_message_parsed.append(textElement.GetParameterizedToken(value));
if (textElement.Token == BANKToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
current_message_raw.append(textElement.GetParameterizedToken());
current_message_parsed.append(textElement.GetParameterizedToken());
temp_bytes_parsed.push_back(value);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
current_message_raw.append("[");
current_message_raw.append(DICTIONARYTOKEN);
current_message_raw.append(":");
current_message_raw.append(core::UppercaseHexWord(dictionary));
current_message_raw.append("]");
int address = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + (dictionary * 2));
int addressEnd = core::Get24LocalFromPC(
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
for (int i = address; i < addressEnd; i++) {
temp_bytes_parsed.push_back(rom()->data()[i]);
current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(value)) {
std::string str = "";
str.push_back(CharEncoder.at(value));
current_message_raw.append(str);
current_message_parsed.append(str);
temp_bytes_parsed.push_back(value);
}
}
// 00074703
}
absl::Status MessageEditor::Cut() {
// Ensure that text is currently selected in the text box.
if (!message_text_box_.text.empty()) {
// Cut the selected text in the control and paste it into the Clipboard.
message_text_box_.Cut();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Paste() {
// Determine if there is any text in the Clipboard to paste into the
if (ImGui::GetClipboardText() != nullptr) {
// Paste the text from the Clipboard into the text box.
message_text_box_.Paste();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Copy() {
// Ensure that text is selected in the text box.
if (message_text_box_.selection_length > 0) {
// Copy the selected text to the Clipboard.
message_text_box_.Copy();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Undo() {
// Determine if last operation can be undone in text box.
if (message_text_box_.can_undo) {
// Undo the last operation.
message_text_box_.Undo();
// clear the undo buffer to prevent last action from being redone.
message_text_box_.clearUndo();
}
return absl::OkStatus();
}
absl::Status MessageEditor::Save() {
std::vector<uint8_t> backup = rom()->vector();
for (int i = 0; i < 100; i++) {
RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, widthArray[i]));
}
int pos = kTextData;
bool inSecondBank = false;
for (const auto& message : ListOfTexts) {
for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->Write(pos, value));
// TODO: 0x80 somehow means the end of the first block. Need to ask Zarby
// for clarification as to why this is the case. Check for the end of the
// first block.
if (value == 0x80) {
// Make sure we didn't go over the space available in the first block.
// 0x7FFF available.
if ((!inSecondBank & pos) > kTextDataEnd) {
DisplayTextOverflowError(pos, true);
// *rom()->data() = backup.data();
// rom()->data() = (uint8_t[])backup.Clone();
return absl::InternalError(
"Too much text data in the first block to save.");
}
// Switch to the second block.
pos = kTextData2 - 1;
inSecondBank = true;
}
pos++;
}
RETURN_IF_ERROR(
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text"
}
// Verify that we didn't go over the space available for the second block.
// 0x14BF available.
if ((inSecondBank & pos) > kTextData2End) {
DisplayTextOverflowError(pos, false);
// rom()->data() = backup;
// return true;
return absl::InternalError(
"Too much text data in the second block to save.");
}
RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
return absl::OkStatus();
}
TextElement MessageEditor::FindMatchingCommand(uint8_t b) {
TextElement empty_element;
for (const auto text_element : TextCommands) {
if (text_element.ID == b) {
return text_element;
}
}
return empty_element;
}
TextElement MessageEditor::FindMatchingSpecial(uint8_t value) {
TextElement empty_element;
for (const auto text_element : SpecialChars) {
if (text_element.ID == value) {
return text_element;
}
}
return empty_element;
}
MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
uint8_t value) {
// return AllDictionaries.First(dictionary = > dictionary.ID == value);
return AllDictionaries[0];
}
uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
uint8_t MessageEditor::FindMatchingCharacter(char value) {
for (const auto [key, char_value] : CharEncoder) {
if (value == char_value) {
return key;
}
}
return 0xFF;
}
bool MessageEditor::SelectMessageID(int id) {
// if (id < textListbox.Items.size()) {
// textListbox.SelectedIndex = id;
// return true;
// }
return false;
}
string MessageEditor::ParseTextDataByte(uint8_t value) {
if (CharEncoder.contains(value)) {
char c = CharEncoder.at(value);
string str = "";
str.push_back(c);
return str;
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
}
return "";
}
void MessageEditor::DrawStringToPreview(string str) {
for (const auto c : str) {
DrawCharacterToPreview(c);
}
}
void MessageEditor::DrawCharacterToPreview(char c) {
DrawCharacterToPreview(FindMatchingCharacter(c));
}
void MessageEditor::DrawCharacterToPreview(std::vector<uint8_t> text) {
for (const auto value : text) {
if (skip_next) {
skip_next = false;
continue;
}
if (value < 100) {
int srcy = value / 16;
int srcx = value - (value & (~0xF));
if (text_pos >= 170) {
text_pos = 0;
text_line++;
}
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, false, false,
1, 2);
text_pos += widthArray[value];
} else if (value == 0x74) {
text_pos = 0;
text_line = 0;
} else if (value == 0x73) {
text_pos = 0;
text_line += 1;
} else if (value == 0x75) {
text_pos = 0;
text_line = 1;
} else if (value == 0x76) {
text_pos = 0;
text_line = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) {
skip_next = true;
continue;
} else if (value == 0x6C) // BCD numbers.
{
DrawCharacterToPreview('0');
skip_next = true;
continue;
} else if (value == 0x6A) {
// Includes parentheses to be longer, since player names can be up to 6
// characters.
DrawStringToPreview("(NAME)");
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
// DictionaryEntry dictionaryEntry =
// GetDictionaryFromID((uint8_t)(value - DICTOFF));
// auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
// if (dictionaryEntry != null) {
// DrawCharacterToPreview(dictionaryEntry.Data);
// }
}
}
}
void MessageEditor::DrawMessagePreview() // From Parsing.
{
// defaultColor = 6;
text_line = 0;
for (int i = 0; i < (172 * 4096); i++) {
currentfontgfx16Ptr[i] = 0;
}
text_pos = 0;
DrawCharacterToPreview(CurrentMessage.Data);
shown_lines = 0;
}
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
bool mirror_x, bool mirror_y, int sizex,
int sizey) {
int drawid = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) {
int mx = xl;
int my = yl;
// Formula information to get tile index position in the array.
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
uint8_t pixel = fontgfx16Ptr[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = x + (y * 172) + (mx * 2) + (my * 172);
if ((pixel & 0x0F) != 0) {
currentfontgfx16Ptr[index + 1] = (uint8_t)((pixel & 0x0F) + (0 * 4));
}
if (((pixel >> 4) & 0x0F) != 0) {
currentfontgfx16Ptr[index + 0] =
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
}
}
}
}
void MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
string bankSTR = bank ? "1st" : "2nd";
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s",
bankSTR, space, posSTR);
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,572 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#include <absl/status/status.h>
#include <absl/strings/str_format.h>
#include <absl/strings/str_replace.h>
#include <absl/strings/str_split.h>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/rom.h"
namespace yaze {
namespace app {
namespace editor {
using std::string;
// TEXT EDITOR RELATED CONSTANTS
const int kGfxFont = 0x70000; // 2bpp format
const int kTextData = 0xE0000;
const int kTextDataEnd = 0xE7FFF;
const int kTextData2 = 0x75F40;
const int kTextData2End = 0x773FF;
const int kPointersDictionaries = 0x74703;
const int kCharactersWidth = 0x74ADF;
const string DICTIONARYTOKEN = "D";
const uint8_t DICTOFF = 0x88;
const uint8_t MESSAGETERMINATOR = 0x7F;
const string BANKToken = "BANK";
const uint8_t BANKID = 0x80;
static int defaultColor = 6;
static std::vector<uint8_t> ParseMessageToData(string str);
static string AddNewLinesToCommands(string str);
static string ReplaceAllDictionaryWords(string str);
struct MessageData {
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect
// them from dictionary replacements.
std::string RawString;
std::string ContentsParsed;
int ID;
std::vector<uint8_t> Data;
std::vector<uint8_t> DataParsed;
int Address;
MessageData() = default;
MessageData(int id, int address, std::string rawString,
std::vector<uint8_t> rawData, std::string parsedString,
std::vector<uint8_t> parsedData)
: ID(id),
Address(address),
RawString(rawString),
Data(rawData),
DataParsed(parsedData),
ContentsParsed(parsedString) {}
void SetMessage(std::string messageString) {
ContentsParsed = messageString;
RawString = OptimizeMessageForDictionary(messageString);
RecalculateData();
}
std::string ToString() {
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
}
std::string GetReadableDumpedContents() {
std::stringstream stringBuilder;
for (const auto& b : Data) {
stringBuilder << absl::StrFormat("%0X ", b);
}
stringBuilder << absl::StrFormat("%00X", MESSAGETERMINATOR);
return absl::StrFormat(
"[[[[\r\nMessage "
"%000X]]]]\r\n[Contents]\r\n%s\r\n\r\n[Data]\r\n%s"
"\r\n\r\n\r\n\r\n",
ID, AddNewLinesToCommands(ContentsParsed), stringBuilder.str());
}
std::string GetDumpedContents() {
return absl::StrFormat("%000X : %s\r\n\r\n", ID, ContentsParsed);
}
std::string OptimizeMessageForDictionary(std::string messageString) {
std::stringstream protons;
bool command = false;
for (const auto& c : messageString) {
if (c == '[') {
command = true;
} else if (c == ']') {
command = false;
}
protons << c;
if (command) {
protons << CHEESE;
}
}
std::string protonsString = protons.str();
std::string replacedString = ReplaceAllDictionaryWords(protonsString);
std::string finalString =
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
return finalString;
}
void RecalculateData() {
Data = ParseMessageToData(RawString);
DataParsed = ParseMessageToData(ContentsParsed);
}
};
struct TextElement {
uint8_t ID;
string Token;
string GenericToken;
string Pattern;
string StrictPattern;
string Description;
bool HasArgument;
TextElement() = default;
TextElement(uint8_t id, string token, bool arg, string description) {
ID = id;
Token = token;
auto format_string = arg ? "[%s:##]" : "[%s]";
GenericToken = format_string + Token;
HasArgument = arg;
Description = description;
Pattern =
arg ? "\\[" + Token + ":?([0-9A-F]{1,2})\\]" : "\\[" + Token + "\\]";
Pattern = absl::StrReplaceAll(Pattern, {{"[", "\\["}, {"]", "\\]"}});
StrictPattern = absl::StrCat("^", Pattern, "$");
StrictPattern = "^" + Pattern + "$";
}
string GetParameterizedToken(uint8_t value = 0) {
if (HasArgument) {
return absl::StrFormat("[%s:%02X]", Token, value);
} else {
return absl::StrFormat("[%s]", Token);
}
}
string ToString() {
return absl::StrFormat("%s %s", GenericToken, Description);
}
std::smatch MatchMe(std::string dfrag) const {
std::regex pattern(StrictPattern);
std::smatch match;
std::regex_match(dfrag, match, pattern);
return match;
}
bool Empty() { return ID == 0; }
};
struct ParsedElement {
TextElement Parent;
uint8_t Value;
bool Active = false;
ParsedElement() = default;
ParsedElement(TextElement textElement, uint8_t value) {
Parent = textElement;
Value = value;
Active = true;
}
};
static ParsedElement FindMatchingElement(string str);
static const TextElement TextCommands[] = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
};
static std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
TextElement(0x4D, "UP", false, "Arrow ↑"),
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
TextElement(0x4F, "LEFT", false, "Arrow ←"),
TextElement(0x50, "RIGHT", false, "Arrow →"),
TextElement(0x5B, "A", false, "Button Ⓐ"),
TextElement(0x5C, "B", false, "Button Ⓑ"),
TextElement(0x5D, "X", false, "Button ⓧ"),
TextElement(0x5E, "Y", false, "Button ⓨ"),
TextElement(0x52, "HP1L", false, "1 HP left"),
TextElement(0x53, "HP1R", false, "1 HP right"),
TextElement(0x54, "HP2L", false, "2 HP left"),
TextElement(0x55, "HP3L", false, "3 HP left"),
TextElement(0x56, "HP3R", false, "3 HP right"),
TextElement(0x57, "HP4L", false, "4 HP left"),
TextElement(0x58, "HP4R", false, "4 HP right"),
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
TextElement(0x4A, "LFL", false, "Link face left"),
TextElement(0x4B, "LFR", false, "Link face right"),
};
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
{0x00, 'A'},
{0x01, 'B'},
{0x02, 'C'},
{0x03, 'D'},
{0x04, 'E'},
{0x05, 'F'},
{0x06, 'G'},
{0x07, 'H'},
{0x08, 'I'},
{0x09, 'J'},
{0x0A, 'K'},
{0x0B, 'L'},
{0x0C, 'M'},
{0x0D, 'N'},
{0x0E, 'O'},
{0x0F, 'P'},
{0x10, 'Q'},
{0x11, 'R'},
{0x12, 'S'},
{0x13, 'T'},
{0x14, 'U'},
{0x15, 'V'},
{0x16, 'W'},
{0x17, 'X'},
{0x18, 'Y'},
{0x19, 'Z'},
{0x1A, 'a'},
{0x1B, 'b'},
{0x1C, 'c'},
{0x1D, 'd'},
{0x1E, 'e'},
{0x1F, 'f'},
{0x20, 'g'},
{0x21, 'h'},
{0x22, 'i'},
{0x23, 'j'},
{0x24, 'k'},
{0x25, 'l'},
{0x26, 'm'},
{0x27, 'n'},
{0x28, 'o'},
{0x29, 'p'},
{0x2A, 'q'},
{0x2B, 'r'},
{0x2C, 's'},
{0x2D, 't'},
{0x2E, 'u'},
{0x2F, 'v'},
{0x30, 'w'},
{0x31, 'x'},
{0x32, 'y'},
{0x33, 'z'},
{0x34, '0'},
{0x35, '1'},
{0x36, '2'},
{0x37, '3'},
{0x38, '4'},
{0x39, '5'},
{0x3A, '6'},
{0x3B, '7'},
{0x3C, '8'},
{0x3D, '9'},
{0x3E, '!'},
{0x3F, '?'},
{0x40, '-'},
{0x41, '.'},
{0x42, ','},
{0x44, '>'},
{0x45, '('},
{0x46, ')'},
{0x4C, '"'},
{0x51, '\''},
{0x59, ' '},
{0x5A, '<'},
// {0x5F, '¡'}, {0x60, '¡'}, {0x61, '¡'}, {0x62, ' '}, {0x63, ' '}, {0x64,
// ' '},
{0x65, ' '},
{0x66, '_'},
};
static TextElement DictionaryElement =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
class MessageEditor : public Editor, public SharedRom {
public:
class TextMessageData {
private:
int ID;
std::string Contents;
std::string ContentsParsed;
std::vector<uint8_t> Data;
std::vector<uint8_t> DataParsed;
public:
TextMessageData(int i, std::string sraw, std::vector<uint8_t> draw,
std::string spar, std::vector<uint8_t> dpar)
: ID(i),
Data(draw),
DataParsed(dpar),
Contents(sraw),
ContentsParsed(spar) {}
void SetMessage(std::string s) {
ContentsParsed = s;
// Contents = OptimizeMessageForDictionary(s);
RecalculateData();
}
void RecalculateData() {
Data = ParseMessageToData(Contents);
DataParsed = ParseMessageToData(ContentsParsed);
}
std::string ToString() {
return absl::StrFormat("%000X - %s", ID, ContentsParsed);
}
std::string GetReadableDumpedContents() {
std::stringstream d;
for (const auto& b : Data) {
d << absl::StrFormat("%00X ", b);
}
return absl::StrFormat(
"[[[[\nMessage %000X]]]]\n[Contents]\n%s\n\n[Data]\n%s\n\n\n\n", ID,
AddNewLinesToCommands(ContentsParsed), d.str());
}
std::string GetDumpedContents() {
return absl::StrFormat("%000X : %s\n\n", ID, ContentsParsed);
}
};
class TextMessageElement {
private:
std::string Token;
std::string Pattern;
std::string StrictPattern;
std::string Description;
bool HasArgument;
uint8_t ID;
std::string GenericToken;
public:
TextMessageElement() = default;
TextMessageElement(uint8_t a, std::string t, bool arg, std::string d)
: Token(t), HasArgument(arg), Description(d), ID(a) {
Pattern =
arg ? absl::StrFormat(
"\\[%s:?([0-9A-F]{1,2})\\]",
std::regex_replace(
Token, std::regex("([][(){}.*+?^$|\\\\])"), R"(\$1)"))
: absl::StrFormat(
"\\[%s\\]",
std::regex_replace(
Token, std::regex("([][(){}.*+?^$|\\\\])"), R"(\$1)"));
StrictPattern = absl::StrFormat("^%s$", Pattern);
GenericToken = arg ? absl::StrFormat("[%s:##]", Token)
: absl::StrFormat("[%s]", Token);
}
std::string GetParameterizedToken(uint8_t b = 0) {
if (HasArgument) {
return absl::StrFormat("[%s:%00X]", Token, b);
} else {
return absl::StrFormat("[%s]", Token);
}
}
std::string ToString() {
return absl::StrFormat("%s %s", GenericToken, Description);
}
std::smatch MatchMe(std::string dfrag) {
std::regex re(StrictPattern);
std::smatch match;
std::regex_search(dfrag, match, re);
return match;
}
};
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
std::vector<uint8_t> Data;
int Length;
std::string Token;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
}
return replacedString;
}
};
MessageEditor() {
type_ = EditorType::kMessage;
// TextCommandList.Items.AddRange(TextCommands);
// SpecialsList.Items.AddRange(SpecialChars);
// pictureBox1.MouseWheel += new
// MouseEventHandler(PictureBox1_MouseWheel);
}
absl::Status Update() override;
void DrawMessageList();
void DrawCurrentMessage();
absl::Status Initialize();
void ReadAllTextData();
void BuildDictionaryEntries();
absl::Status Cut() override;
absl::Status Copy() override;
absl::Status Paste() override;
absl::Status Undo() override;
absl::Status Redo() override {
return absl::UnimplementedError("Redo not implemented");
}
absl::Status Find() override {
return absl::UnimplementedError("Find not implemented");
}
absl::Status Save();
TextElement FindMatchingCommand(uint8_t byte);
TextElement FindMatchingSpecial(uint8_t value);
string ParseTextDataByte(uint8_t value);
DictionaryEntry GetDictionaryFromID(uint8_t value);
static uint8_t FindDictionaryEntry(uint8_t value);
static uint8_t FindMatchingCharacter(char value);
bool SelectMessageID(int id);
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
bool mirror_x = false, bool mirror_y = false,
int sizex = 1, int sizey = 1);
void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(std::vector<uint8_t> text);
void DrawStringToPreview(string str);
void DrawMessagePreview();
void DisplayTextOverflowError(int pos, bool bank);
static const std::vector<DictionaryEntry> AllDicts;
uint8_t widthArray[100];
string romname = "";
int text_line = 0;
int text_pos = 0;
int shown_lines = 0;
int selected_tile = 0;
bool skip_next = false;
bool from_form = false;
std::vector<MessageData> ListOfTexts;
std::vector<MessageData> DisplayedMessages;
MessageData CurrentMessage;
private:
static const TextElement DictionaryElement;
bool data_loaded_ = false;
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
gui::Canvas current_font_gfx16_canvas_;
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
Bytes fontgfx16Ptr;
Bytes currentfontgfx16Ptr;
gfx::SnesPalette previewColors;
struct TextBox {
std::string text;
std::string buffer;
int cursor_pos = 0;
int selection_start = 0;
int selection_end = 0;
int selection_length = 0;
bool has_selection = false;
bool has_focus = false;
bool changed = false;
bool can_undo = false;
void Undo() {
text = buffer;
cursor_pos = selection_start;
has_selection = false;
}
void clearUndo() { can_undo = false; }
void Copy() { ImGui::SetClipboardText(text.c_str()); }
void Cut() {
Copy();
text.erase(selection_start, selection_end - selection_start);
cursor_pos = selection_start;
has_selection = false;
changed = true;
}
void Paste() {
text.erase(selection_start, selection_end - selection_start);
text.insert(selection_start, ImGui::GetClipboardText());
std::string str = ImGui::GetClipboardText();
cursor_pos = selection_start + str.size();
has_selection = false;
changed = true;
}
};
TextBox message_text_box_;
};
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H

View File

@@ -21,12 +21,13 @@ enum class EditorType {
kPalette,
kScreen,
kSprite,
kMessage,
kSettings,
};
constexpr std::array<const char*, 8> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music",
"Overworld", "Palette", "Screen", "Sprite",
constexpr std::array<const char*, 10> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Settings",
};
/**