add MessageEditor
This commit is contained in:
@@ -13,6 +13,7 @@ set(
|
|||||||
app/editor/overworld_editor.cc
|
app/editor/overworld_editor.cc
|
||||||
app/editor/sprite/sprite_editor.cc
|
app/editor/sprite/sprite_editor.cc
|
||||||
app/editor/music/music_editor.cc
|
app/editor/music/music_editor.cc
|
||||||
|
app/editor/message/message_editor.cc
|
||||||
app/editor/code/assembly_editor.cc
|
app/editor/code/assembly_editor.cc
|
||||||
app/editor/graphics/screen_editor.cc
|
app/editor/graphics/screen_editor.cc
|
||||||
app/editor/graphics/graphics_editor.cc
|
app/editor/graphics/graphics_editor.cc
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ void MasterEditor::ManageActiveEditors() {
|
|||||||
active_editors_.push_back(&assembly_editor_);
|
active_editors_.push_back(&assembly_editor_);
|
||||||
ImGui::CloseCurrentPopup();
|
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,
|
if (ImGui::MenuItem("Settings", nullptr, false,
|
||||||
!IsEditorActive(&settings_editor_, active_editors_))) {
|
!IsEditorActive(&settings_editor_, active_editors_))) {
|
||||||
active_editors_.push_back(&settings_editor_);
|
active_editors_.push_back(&settings_editor_);
|
||||||
@@ -257,6 +262,13 @@ void MasterEditor::ManageActiveEditors() {
|
|||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case EditorType::kMessage:
|
||||||
|
if (ImGui::BeginTabItem("Message", &open)) {
|
||||||
|
current_editor_ = &message_editor_;
|
||||||
|
status_ = message_editor_.Update();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "app/editor/graphics/graphics_editor.h"
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
#include "app/editor/graphics/palette_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/editor/graphics/screen_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/music/music_editor.h"
|
||||||
#include "app/editor/overworld_editor.h"
|
#include "app/editor/overworld_editor.h"
|
||||||
#include "app/editor/settings_editor.h"
|
#include "app/editor/settings_editor.h"
|
||||||
@@ -123,6 +124,7 @@ class MasterEditor : public SharedRom,
|
|||||||
ScreenEditor screen_editor_;
|
ScreenEditor screen_editor_;
|
||||||
SpriteEditor sprite_editor_;
|
SpriteEditor sprite_editor_;
|
||||||
SettingsEditor settings_editor_;
|
SettingsEditor settings_editor_;
|
||||||
|
MessageEditor message_editor_;
|
||||||
MemoryEditorWithDiffChecker memory_editor_;
|
MemoryEditorWithDiffChecker memory_editor_;
|
||||||
|
|
||||||
ImVector<int> active_tabs_;
|
ImVector<int> active_tabs_;
|
||||||
|
|||||||
678
src/app/editor/message/message_editor.cc
Normal file
678
src/app/editor/message/message_editor.cc
Normal 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
|
||||||
572
src/app/editor/message/message_editor.h
Normal file
572
src/app/editor/message/message_editor.h
Normal 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
|
||||||
@@ -21,12 +21,13 @@ enum class EditorType {
|
|||||||
kPalette,
|
kPalette,
|
||||||
kScreen,
|
kScreen,
|
||||||
kSprite,
|
kSprite,
|
||||||
|
kMessage,
|
||||||
kSettings,
|
kSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array<const char*, 8> kEditorNames = {
|
constexpr std::array<const char*, 10> kEditorNames = {
|
||||||
"Assembly", "Dungeon", "Graphics", "Music",
|
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
|
||||||
"Overworld", "Palette", "Screen", "Sprite",
|
"Palette", "Screen", "Sprite", "Message", "Settings",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user