OverworldEntity update: Entrances, Exits, Items

This commit is contained in:
scawful
2024-01-27 15:55:47 -05:00
parent c4a44fbc10
commit e086f12ade
19 changed files with 1009 additions and 384 deletions

View File

@@ -309,6 +309,11 @@ void DungeonEditor::DrawDungeonTabView() {
for (int n = 0; n < active_rooms_.Size;) {
bool open = true;
if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) {
active_rooms_.erase(active_rooms_.Data + n);
continue;
}
if (ImGui::BeginTabItem(
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open,
ImGuiTabItemFlags_None)) {

View File

@@ -42,6 +42,8 @@ class DungeonEditor : public Editor,
absl::Status Undo() override { return absl::OkStatus(); }
absl::Status Redo() override { return absl::OkStatus(); }
void add_room(int i) { active_rooms_.push_back(i); }
private:
void UpdateDungeonRoomView();

View File

@@ -4,6 +4,7 @@
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_internal.h>
#include <imgui_memory_editor.h>
#include "absl/status/status.h"
@@ -122,6 +123,31 @@ void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer) {
rom()->SetupRenderer(renderer);
}
namespace {
// Function to switch the active tab in a tab bar
void SetTabBarTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) {
if (tab_bar == NULL) return;
// Find the tab item with the specified tab_id
// for (int i = 0; i < tab_bar->Tabs.Size; i++) {
ImGuiTabItem* tab_item = &tab_bar->Tabs[tab_id];
// if (tab_item->ID == tab_id) {
// Set the tab item as active
tab_item->LastFrameVisible = -1;
tab_item->LastFrameSelected = -1;
tab_bar->VisibleTabId = tab_id;
tab_bar->VisibleTabWasSubmitted = true;
tab_bar->SelectedTabId = tab_id;
tab_bar->NextSelectedTabId = tab_id;
tab_bar->ReorderRequestTabId = tab_id;
tab_bar->CurrFrameVisible = -1;
// break;
// }
// }
}
} // namespace
absl::Status MasterEditor::Update() {
NewMasterFrame();
@@ -138,15 +164,22 @@ absl::Status MasterEditor::Update() {
}
TAB_BAR("##TabBar")
auto current_tab_bar = ImGui::GetCurrentContext()->CurrentTabBar;
gui::RenderTabItem("Overworld", [&]() {
current_editor_ = &overworld_editor_;
status_ = overworld_editor_.Update();
});
if (overworld_editor_.jump_to_tab() == -1) {
gui::RenderTabItem("Overworld", [&]() {
current_editor_ = &overworld_editor_;
status_ = overworld_editor_.Update();
});
}
gui::RenderTabItem("Dungeon", [&]() {
current_editor_ = &dungeon_editor_;
status_ = dungeon_editor_.Update();
if (overworld_editor_.jump_to_tab() != -1) {
dungeon_editor_.add_room(overworld_editor_.jump_to_tab());
overworld_editor_.jump_to_tab_ = -1;
}
});
gui::RenderTabItem("Graphics",
@@ -336,6 +369,8 @@ void MasterEditor::DrawFileMenu() {
&mutable_flags()->overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&mutable_flags()->overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&mutable_flags()->overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&mutable_flags()->overworld.kSaveOverworldProperties);
ImGui::EndMenu();
@@ -537,16 +572,16 @@ void MasterEditor::DrawHelpMenu() {
void MasterEditor::SaveRom() {
if (flags()->kSaveDungeonMaps) {
status_ = screen_editor_.SaveDungeonMaps();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldMaps) {
if (overworld_editor_.overworld()->CreateTile32Tilemap()) {
status_ = overworld_editor_.overworld()->SaveMap16Tiles();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveMap32Tiles();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
status_ = overworld_editor_.overworld()->SaveOverworldMaps();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
} else {
status_ = absl::InternalError(
"Failed to save Overworld maps, aborting ROM save.");
@@ -555,15 +590,19 @@ void MasterEditor::SaveRom() {
}
if (flags()->overworld.kSaveOverworldEntrances) {
status_ = overworld_editor_.overworld()->SaveEntrances();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldExits) {
status_ = overworld_editor_.overworld()->SaveExits();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldItems) {
status_ = overworld_editor_.overworld()->SaveItems();
RETURN_VOID_IF_ERROR(status_);
}
if (flags()->overworld.kSaveOverworldProperties) {
status_ = overworld_editor_.overworld()->SaveMapProperties();
PRINT_IF_ERROR(status_);
RETURN_VOID_IF_ERROR(status_);
}
status_ = rom()->SaveToFile(backup_rom_);

View File

@@ -1,6 +1,8 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
#define IMGUI_DEFINE_MATH_OPERATORS 1
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>

View File

@@ -74,10 +74,6 @@ absl::Status OverworldEditor::Update() {
static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings;
// We demonstrate using the full viewport area or the work area (without
// menu-bars, task-bars etc.) Based on your use case you may want one or the
// other.
const ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos);
ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize
@@ -109,9 +105,7 @@ absl::Status OverworldEditor::Update() {
}
absl::Status OverworldEditor::UpdateOverworldEdit() {
// Draws the toolset for editing the Overworld.
RETURN_IF_ERROR(DrawToolset())
if (BeginTable(kOWEditTable.data(), 2, kOWEditFlags, ImVec2(0, 0))) {
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
@@ -127,100 +121,6 @@ absl::Status OverworldEditor::UpdateOverworldEdit() {
return absl::OkStatus();
}
absl::Status OverworldEditor::UpdateUsageStats() {
if (BeginTable("##UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) {
TableSetupColumn("Entrances");
TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
for (int i = 0; i < 0x81; i++) {
std::string str = absl::StrFormat("%#x", i);
if (ImGui::Selectable(str.c_str(), selected_entrance_ == i,
overworld_.Entrances().at(i).deleted
? ImGuiSelectableFlags_Disabled
: 0)) {
selected_entrance_ = i;
selected_usage_map_ = overworld_.Entrances().at(i).map_id_;
properties_canvas_.set_highlight_tile_id(selected_usage_map_);
}
}
ImGui::EndChild();
TableNextColumn();
DrawUsageGrid();
TableNextColumn();
DrawOverworldProperties();
ImGui::EndTable();
}
return absl::OkStatus();
}
void OverworldEditor::CalculateUsageStats() {
absl::flat_hash_map<uint16_t, int> entrance_usage;
for (auto each_entrance : overworld_.Entrances()) {
if (each_entrance.map_id_ < 0x40 + (current_world_ * 0x40) &&
each_entrance.map_id_ >= (current_world_ * 0x40)) {
entrance_usage[each_entrance.entrance_id_]++;
}
}
}
void OverworldEditor::DrawUsageGrid() {
// Create a grid of 8x8 squares
int totalSquares = 128;
int squaresWide = 8;
int squaresTall = (totalSquares + squaresWide - 1) /
squaresWide; // Ceiling of totalSquares/squaresWide
// Loop through each row
for (int row = 0; row < squaresTall; ++row) {
ImGui::NewLine();
for (int col = 0; col < squaresWide; ++col) {
if (row * squaresWide + col >= totalSquares) {
break;
}
// Determine if this square should be highlighted
bool highlight = selected_usage_map_ == (row * squaresWide + col);
// Set highlight color if needed
if (highlight) {
ImGui::PushStyleColor(
ImGuiCol_Button,
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
}
// Create a button or selectable for each square
if (ImGui::Button("##square", ImVec2(20, 20))) {
// Switch over to the room editor tab
// and add a room tab by the ID of the square
// that was clicked
}
// Reset style if it was highlighted
if (highlight) {
ImGui::PopStyleColor();
}
// Check if the square is hovered
if (ImGui::IsItemHovered()) {
// Display a tooltip with all the room properties
}
// Keep squares in the same line
ImGui::SameLine();
}
}
}
// ----------------------------------------------------------------------------
absl::Status OverworldEditor::DrawToolset() {
static bool show_gfx_group = false;
static bool show_properties = false;
@@ -369,35 +269,6 @@ absl::Status OverworldEditor::DrawToolset() {
return absl::OkStatus();
}
void OverworldEditor::DrawOverworldProperties() {
static bool init_properties = false;
if (!init_properties) {
for (int i = 0; i < 0x40; i++) {
std::string area_graphics_str = absl::StrFormat(
"0x%02hX", overworld_.overworld_map(i)->area_graphics());
properties_canvas_.mutable_labels(0)->push_back(area_graphics_str);
}
for (int i = 0; i < 0x40; i++) {
std::string area_palette_str = absl::StrFormat(
"0x%02hX", overworld_.overworld_map(i)->area_palette());
properties_canvas_.mutable_labels(1)->push_back(area_palette_str);
}
init_properties = true;
}
if (ImGui::Button("Area Graphics")) {
properties_canvas_.set_current_labels(0);
}
if (ImGui::Button("Area Palette")) {
properties_canvas_.set_current_labels(1);
}
properties_canvas_.UpdateInfoGrid(ImVec2(512, 512), 16, 1.0f, 64);
}
// ----------------------------------------------------------------------------
void OverworldEditor::RefreshChildMap(int map_index) {
@@ -572,9 +443,25 @@ void OverworldEditor::DrawOverworldMapSettings() {
// ----------------------------------------------------------------------------
namespace {
void MoveEntranceOnGrid(zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0,
ImVec2 scrolling) {
namespace entity_internal {
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
ImVec2 canvas_p0, ImVec2 scrolling) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Check if the mouse is hovering over the entity
if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) {
return true;
}
return false;
}
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool free_movement = false) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
@@ -583,151 +470,203 @@ void MoveEntranceOnGrid(zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0,
// Calculate the new position on the 16x16 grid
int new_x = static_cast<int>(mouse_pos.x) / 16 * 16;
int new_y = static_cast<int>(mouse_pos.y) / 16 * 16;
if (free_movement) {
new_x = static_cast<int>(mouse_pos.x) / 8 * 8;
new_y = static_cast<int>(mouse_pos.y) / 8 * 8;
}
// Update the entrance position
entrance.x_ = new_x;
entrance.y_ = new_y;
// Update the entity position
entity->set_x(new_x);
entity->set_y(new_y);
}
void DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) {
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
ImVec2 scrolling, bool &is_dragging_entity,
zelda3::OverworldEntity *&dragged_entity,
zelda3::OverworldEntity *&current_entity,
bool free_movement = false) {
std::string entity_type = "Entity";
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) {
entity_type = "Exit";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) {
entity_type = "Entrance";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) {
entity_type = "Sprite";
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) {
entity_type = "Item";
}
if (IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling) &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left) && !is_dragging_entity) {
dragged_entity = entity;
is_dragging_entity = true;
} else if (IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
current_entity = entity;
ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str());
} else if (is_dragging_entity && dragged_entity == entity &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
entity->UpdateMapProperties(entity->map_id_);
is_dragging_entity = false;
dragged_entity = nullptr;
} else if (is_dragging_entity && dragged_entity == entity) {
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
sizeof(zelda3::OverworldEntity));
Text("Moving %s ID: %s", entity_type.c_str(),
core::UppercaseHexByte(entity->entity_id_).c_str());
ImGui::EndDragDropSource();
}
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
entity->x_ = dragged_entity->x_;
entity->y_ = dragged_entity->y_;
entity->UpdateMapProperties(entity->map_id_);
}
}
} // namespace entity_internal
namespace entrance_internal {
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Entrance editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
static uint16_t map_id = entrance.map_id_;
static int entrance_id = entrance.entrance_id_;
static int x = entrance.x_;
static int y = entrance.y_;
gui::InputHex("Map ID", &entrance.map_id_);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
kInputFieldSize + 20);
gui::InputHex("X", &entrance.x_);
gui::InputHex("Y", &entrance.y_);
gui::InputHexWord("Map ID", &map_id, kInputFieldSize + 20);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_, kInputFieldSize);
ImGui::SetNextItemWidth(100.f);
ImGui::InputInt("X", &x);
ImGui::SetNextItemWidth(100.f);
ImGui::InputInt("Y", &y);
if (ImGui::Button("OK")) {
// Implement what happens when OK is pressed
entrance.map_id_ = map_id;
entrance.entrance_id_ = entrance_id;
entrance.x_ = x;
entrance.y_ = y;
if (ImGui::Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
// Implement what happens when Cancel is pressed
if (ImGui::Button(ICON_MD_CANCEL)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
} // namespace
} // namespace entrance_internal
bool OverworldEditor::IsMouseHoveringOverEntrance(
const zelda3::OverworldEntrance &entrance, ImVec2 canvas_p0,
ImVec2 scrolling) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Check if the mouse is hovering over the entrance
if (mouse_pos.x >= entrance.x_ && mouse_pos.x <= entrance.x_ + 16 &&
mouse_pos.y >= entrance.y_ && mouse_pos.y <= entrance.y_ + 16) {
return true;
}
return false;
}
void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0,
ImVec2 scrolling) {
void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling,
bool holes) {
int i = 0;
for (auto &each : overworld_.Entrances()) {
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
each.map_id_ >= (current_world_ * 0x40)) {
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
ImVec4(210, 24, 210, 150));
each.map_id_ >= (current_world_ * 0x40) && !each.deleted) {
// Make this yellow
auto color = ImVec4(255, 255, 0, 100);
if (each.is_hole_) {
color = ImVec4(255, 255, 255, 200);
}
if (each.deleted) {
color = ImVec4(0, 0, 0, 0);
}
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color);
std::string str = core::UppercaseHexByte(each.entrance_id_);
// Check if this entrance is being clicked and dragged
if (current_mode == EditingMode::ENTRANCES) {
if (IsMouseHoveringOverEntrance(each, canvas_p0, scrolling) &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
dragged_entrance_ = &each;
is_dragging_entrance_ = true;
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("ENTRANCE_PAYLOAD", &each,
sizeof(zelda3::OverworldEntrance));
Text("Moving Entrance ID: %s", str.c_str());
ImGui::EndDragDropSource();
}
} else if (IsMouseHoveringOverEntrance(each, canvas_p0, scrolling) &&
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
current_entrance_ = &each;
ImGui::OpenPopup("Entrance editor");
} else if (is_dragging_entrance_ && dragged_entrance_ == &each &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
MoveEntranceOnGrid(*dragged_entrance_, canvas_p0, scrolling);
each.x_ = dragged_entrance_->x_;
each.y_ = dragged_entrance_->y_;
each.UpdateMapProperties(each.map_id_);
is_dragging_entrance_ = false;
dragged_entrance_ = nullptr;
} else if (is_dragging_entrance_ && dragged_entrance_ == &each) {
MoveEntranceOnGrid(*dragged_entrance_, canvas_p0, scrolling);
each.x_ = dragged_entrance_->x_;
each.y_ = dragged_entrance_->y_;
each.UpdateMapProperties(each.map_id_);
entity_internal::HandleEntityDragging(&each, canvas_p0, scrolling,
is_dragging_entity_,
dragged_entity_, current_entity_);
if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0,
scrolling) &&
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
jump_to_tab_ = each.entrance_id_;
}
if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0,
scrolling) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
current_entrance_id_ = i;
current_entrance_ = each;
}
}
ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2);
ow_map_canvas_.DrawText(str, each.x_, each.y_);
}
i++;
}
DrawOverworldEntrancePopup(*current_entrance_);
}
namespace {
bool IsMouseHoveringOverExit(const zelda3::OverworldExit &exit,
ImVec2 canvas_p0, ImVec2 scrolling) {
// Get the mouse position relative to the canvas
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Check if the mouse is hovering over the entrance
if (mouse_pos.x >= exit.x_ && mouse_pos.x <= exit.x_ + 16 &&
mouse_pos.y >= exit.y_ && mouse_pos.y <= exit.y_ + 16) {
return true;
if (entrance_internal::DrawOverworldEntrancePopup(
overworld_.Entrances()[current_entrance_id_])) {
overworld_.Entrances()[current_entrance_id_] = current_entrance_;
}
return false;
}
void DrawExitEditorPopup(zelda3::OverworldExit &exit) {
namespace exit_internal {
bool DrawExitEditorPopup(zelda3::OverworldExit &exit) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Exit editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
static int room = 385;
static int scrollY = 0, centerY = 143, yPos = 128;
static int scrollX = 256, centerX = 397, xPos = 496;
static int doorType = 0; // Normal door: None = 0, Wooden = 1, Bombable = 2
static int fancyDoorType =
0; // Fancy door: None = 0, Sanctuary = 1, Palace = 2
static int map = 128, unk1 = 0, unk2 = 0;
static int linkPosture = 2, spriteGFX = 12;
static int bgGFX = 47, palette = 10, sprPal = 8;
static int top = 0, bottom = 32, left = 256, right = 256;
// Normal door: None = 0, Wooden = 1, Bombable = 2
static int doorType = exit.door_type_1_;
// Fancy door: None = 0, Sanctuary = 1, Palace = 2
static int fancyDoorType = exit.door_type_2_;
static int xPos = 0;
static int yPos = 0;
// Special overworld exit properties
static int centerY = 0;
static int centerX = 0;
static int unk1 = 0;
static int unk2 = 0;
static int linkPosture = 0;
static int spriteGFX = 0;
static int bgGFX = 0;
static int palette = 0;
static int sprPal = 0;
static int top = 0;
static int bottom = 0;
static int left = 0;
static int right = 0;
static int leftEdgeOfMap = 0;
ImGui::InputScalar("Room", ImGuiDataType_U8, &exit.room_id_);
ImGui::InputInt("Scroll Y", &scrollY);
ImGui::InputInt("Center Y", &centerY);
ImGui::InputInt("Y pos", &yPos);
ImGui::InputInt("Scroll X", &scrollX);
ImGui::InputInt("Center X", &centerX);
ImGui::InputInt("X pos", &xPos);
gui::InputHexWord("Room", &exit.room_id_);
ImGui::SameLine();
gui::InputHex("Entity ID", &exit.entity_id_, 4);
gui::InputHex("Map", &exit.map_id_);
ImGui::SameLine();
ImGui::Checkbox("Automatic", &exit.is_automatic_);
gui::InputHex("X Positon", &exit.x_);
ImGui::SameLine();
gui::InputHex("Y Position", &exit.y_);
gui::InputHexByte("X Camera", &exit.x_camera_);
ImGui::SameLine();
gui::InputHexByte("Y Camera", &exit.y_camera_);
gui::InputHexWord("X Scroll", &exit.x_scroll_);
ImGui::SameLine();
gui::InputHexWord("Y Scroll", &exit.y_scroll_);
ImGui::Separator();
static bool show_properties = false;
ImGui::Checkbox("Show properties", &show_properties);
if (show_properties) {
ImGui::Text("Deleted? %s", exit.deleted ? "true" : "false");
ImGui::Text("Hole? %s", exit.is_hole_ ? "true" : "false");
ImGui::Text("Large Map? %s", exit.large_map_ ? "true" : "false");
}
gui::TextWithSeparators("Unimplemented below");
ImGui::RadioButton("None", &doorType, 0);
ImGui::SameLine();
@@ -736,102 +675,205 @@ void DrawExitEditorPopup(zelda3::OverworldExit &exit) {
ImGui::RadioButton("Bombable", &doorType, 2);
// If door type is not None, input positions
if (doorType != 0) {
ImGui::InputInt("Door X pos",
&xPos); // Placeholder for door's X position
ImGui::InputInt("Door Y pos",
&yPos); // Placeholder for door's Y position
gui::InputHex("Door X pos", &xPos);
gui::InputHex("Door Y pos", &yPos);
}
ImGui::RadioButton("None##Fancy", &fancyDoorType, 0);
ImGui::SameLine();
ImGui::RadioButton("Sanctuary", &fancyDoorType
,
1);
ImGui::RadioButton("Sanctuary", &fancyDoorType, 1);
ImGui::SameLine();
ImGui::RadioButton("Palace", &fancyDoorType, 2);
// If fancy door type is not None, input positions
if (fancyDoorType != 0) {
// Placeholder for fancy door's X position
ImGui::InputInt("Fancy Door X pos", &xPos);
gui::InputHex("Fancy Door X pos", &xPos);
// Placeholder for fancy door's Y position
ImGui::InputInt("Fancy Door Y pos", &yPos);
gui::InputHex("Fancy Door Y pos", &yPos);
}
ImGui::InputInt("Map", &map);
ImGui::InputInt("Unk1", &unk1);
ImGui::InputInt("Unk2", &unk2);
static bool special_exit = false;
ImGui::Checkbox("Special exit", &special_exit);
if (special_exit) {
gui::InputHex("Center X", &centerX);
ImGui::InputInt("Link's posture", &linkPosture);
ImGui::InputInt("Sprite GFX", &spriteGFX);
ImGui::InputInt("BG GFX", &bgGFX);
ImGui::InputInt("Palette", &palette);
ImGui::InputInt("Spr Pal", &sprPal);
gui::InputHex("Center Y", &centerY);
gui::InputHex("Unk1", &unk1);
gui::InputHex("Unk2", &unk2);
ImGui::InputInt("Top", &top);
ImGui::InputInt("Bottom", &bottom);
ImGui::InputInt("Left", &left);
ImGui::InputInt("Right", &right);
gui::InputHex("Link's posture", &linkPosture);
gui::InputHex("Sprite GFX", &spriteGFX);
gui::InputHex("BG GFX", &bgGFX);
gui::InputHex("Palette", &palette);
gui::InputHex("Spr Pal", &sprPal);
ImGui::InputInt("Left edge of map", &leftEdgeOfMap);
gui::InputHex("Top", &top);
gui::InputHex("Bottom", &bottom);
gui::InputHex("Left", &left);
gui::InputHex("Right", &right);
if (ImGui::Button("OK")) {
// Implement what happens when OK is pressed
gui::InputHex("Left edge of map", &leftEdgeOfMap);
}
if (ImGui::Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
// Implement what happens when Cancel is pressed
if (ImGui::Button(ICON_MD_CANCEL)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_DELETE)) {
exit.deleted = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
} // namespace
} // namespace exit_internal
void OverworldEditor::DrawOverworldExits(ImVec2 canvas_p0, ImVec2 scrolling) {
int i = 0;
for (auto &each : *overworld_.exits()) {
for (auto &each : *overworld_.mutable_exits()) {
if (each.map_id_ < 0x40 + (current_world_ * 0x40) &&
each.map_id_ >= (current_world_ * 0x40)) {
each.map_id_ >= (current_world_ * 0x40) && !each.deleted) {
ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16,
ImVec4(255, 255, 255, 150));
std::string str = absl::StrFormat("%#x", each.entrance_id_);
ow_map_canvas_.DrawText(str, each.x_ - 4, each.y_ - 2);
if (current_mode == EditingMode::EXITS) {
each.entity_id_ = i;
entity_internal::HandleEntityDragging(
&each, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
is_dragging_entity_, dragged_entity_, current_entity_, true);
// Check if this entrance is being clicked and dragged
if (IsMouseHoveringOverExit(each, canvas_p0, scrolling) &&
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
current_exit_ = i;
ImGui::OpenPopup("Exit editor");
if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0,
scrolling) &&
ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
jump_to_tab_ = each.room_id_;
}
if (entity_internal::IsMouseHoveringOverEntity(each, canvas_p0,
scrolling) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
current_exit_id_ = i;
current_exit_ = each;
current_entity_ = &each;
current_entity_->entity_id_ = i;
ImGui::OpenPopup("Exit editor");
}
}
std::string str = core::UppercaseHexByte(i);
ow_map_canvas_.DrawText(str, each.x_, each.y_);
}
i++;
}
DrawExitEditorPopup(overworld_.mutable_exits()->at(current_exit_));
if (exit_internal::DrawExitEditorPopup(
overworld_.mutable_exits()->at(current_exit_id_))) {
overworld_.mutable_exits()->at(current_exit_id_) = current_exit_;
}
}
namespace item_internal {
bool DrawItemEditorPopup(zelda3::OverworldItem &item) {
static bool set_done = false;
if (set_done) {
set_done = false;
}
if (ImGui::BeginPopupModal("Item editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::BeginChild("ScrollRegion", ImVec2(0, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup();
for (int i = 0; i < zelda3::kSecretItemNames.size(); i++) {
if (ImGui::Selectable(zelda3::kSecretItemNames[i].c_str(),
item.id == i)) {
item.id = i;
}
}
ImGui::EndGroup();
ImGui::EndChild();
if (ImGui::Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CLOSE)) {
set_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_DELETE)) {
item.deleted = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
} // namespace item_internal
void OverworldEditor::DrawOverworldItems() {
int i = 0;
for (auto &item : *overworld_.mutable_all_items()) {
// Get the item's bitmap and real X and Y positions
if (item.room_map_id < 0x40 + (current_world_ * 0x40) &&
item.room_map_id >= (current_world_ * 0x40) && !item.deleted) {
std::string item_name = zelda3::kSecretItemNames[item.id];
ow_map_canvas_.DrawRect(item.x_, item.y_, 16, 16, ImVec4(255, 0, 0, 150));
if (current_mode == EditingMode::ITEMS) {
if (entity_internal::IsMouseHoveringOverEntity(
item, ow_map_canvas_.zero_point(),
ow_map_canvas_.scrolling()) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
current_item_id_ = i;
current_item_ = item;
}
// Check if this item is being clicked and dragged
entity_internal::HandleEntityDragging(
&item, ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling(),
is_dragging_entity_, dragged_entity_, current_entity_);
}
ow_map_canvas_.DrawText(item_name, item.x_, item.y_);
}
i++;
}
if (item_internal::DrawItemEditorPopup(
overworld_.mutable_all_items()->at(current_item_id_))) {
overworld_.mutable_all_items()->at(current_item_id_) = current_item_;
}
}
// ----------------------------------------------------------------------------
void OverworldEditor::DrawOverworldSprites() {
for (const auto &sprite : overworld_.Sprites(game_state_)) {
// Get the sprite's bitmap and real X and Y positions
auto id = sprite.id();
const gfx::Bitmap &sprite_bitmap = sprite_previews_[id];
int realX = sprite.GetRealX();
int realY = sprite.GetRealY();
for (auto &sprite : *overworld_.mutable_sprites(game_state_)) {
int map_id = sprite.map_id();
int map_x = sprite.map_x();
int map_y = sprite.map_y();
// Draw the sprite's bitmap onto the canvas at its real X and Y positions
ow_map_canvas_.DrawBitmap(sprite_bitmap, realX, realY);
ow_map_canvas_.DrawRect(realX, realY, sprite.Width(), sprite.Height(),
ImVec4(255, 0, 0, 150));
std::string str = absl::StrFormat("%s", sprite.Name());
ow_map_canvas_.DrawText(str, realX - 4, realY - 2);
if (map_id < 0x40 + (current_world_ * 0x40) &&
map_id >= (current_world_ * 0x40) && !sprite.deleted()) {
// auto globalx = (((map_id & 0x7) * 512) + (map_x * 16));
// auto globaly = (((map_id & 0x3F) / 8 * 512) + (map_y * 16));
ow_map_canvas_.DrawRect(map_x, map_y, 16, 16,
/*magenta*/ ImVec4(255, 0, 255, 150));
ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.Name()), map_x,
map_y);
}
}
}
@@ -956,7 +998,7 @@ void OverworldEditor::CheckForOverworldEdits() {
}
void OverworldEditor::CheckForCurrentMap() {
// 4096x4096, 512x512 maps and some are larges maps 1024x1024
// 4096x4096, 512x512 maps and some are larges maps 1024x1024
auto mouse_position = ImGui::GetIO().MousePos;
constexpr int small_map_size = 512;
auto large_map_size = 1024;
@@ -1008,9 +1050,8 @@ void OverworldEditor::DrawOverworldCanvas() {
DrawOverworldEntrances(ow_map_canvas_.zero_point(),
ow_map_canvas_.scrolling());
DrawOverworldExits(ow_map_canvas_.zero_point(), ow_map_canvas_.scrolling());
if (flags()->overworld.kDrawOverworldSprites) {
DrawOverworldSprites();
}
DrawOverworldItems();
DrawOverworldSprites();
if (ImGui::IsItemHovered()) CheckForCurrentMap();
CheckForOverworldEdits();
}
@@ -1187,6 +1228,35 @@ absl::Status OverworldEditor::LoadSpriteGraphics() {
return absl::OkStatus();
}
void OverworldEditor::DrawOverworldProperties() {
static bool init_properties = false;
if (!init_properties) {
for (int i = 0; i < 0x40; i++) {
std::string area_graphics_str = absl::StrFormat(
"0x%02hX", overworld_.overworld_map(i)->area_graphics());
properties_canvas_.mutable_labels(0)->push_back(area_graphics_str);
}
for (int i = 0; i < 0x40; i++) {
std::string area_palette_str = absl::StrFormat(
"0x%02hX", overworld_.overworld_map(i)->area_palette());
properties_canvas_.mutable_labels(1)->push_back(area_palette_str);
}
init_properties = true;
}
if (ImGui::Button("Area Graphics")) {
properties_canvas_.set_current_labels(0);
}
if (ImGui::Button("Area Palette")) {
properties_canvas_.set_current_labels(1);
}
properties_canvas_.UpdateInfoGrid(ImVec2(512, 512), 16, 1.0f, 64);
}
absl::Status OverworldEditor::DrawExperimentalModal() {
ImGui::Begin("Experimental", &show_experimental);
@@ -1236,6 +1306,127 @@ absl::Status OverworldEditor::DrawExperimentalModal() {
return absl::OkStatus();
}
absl::Status OverworldEditor::UpdateUsageStats() {
if (BeginTable("##UsageStatsTable", 3, kOWEditFlags, ImVec2(0, 0))) {
TableSetupColumn("Entrances");
TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch,
ImGui::GetContentRegionAvail().x);
TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256);
TableHeadersRow();
TableNextRow();
TableNextColumn();
ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
ImGuiWindowFlags_HorizontalScrollbar);
for (int i = 0; i < 0x81; i++) {
std::string str = absl::StrFormat("%#x", i);
if (ImGui::Selectable(str.c_str(), selected_entrance_ == i,
overworld_.Entrances().at(i).deleted
? ImGuiSelectableFlags_Disabled
: 0)) {
selected_entrance_ = i;
selected_usage_map_ = overworld_.Entrances().at(i).map_id_;
properties_canvas_.set_highlight_tile_id(selected_usage_map_);
}
}
ImGui::EndChild();
TableNextColumn();
DrawUsageGrid();
TableNextColumn();
DrawOverworldProperties();
ImGui::EndTable();
}
return absl::OkStatus();
}
void OverworldEditor::CalculateUsageStats() {
absl::flat_hash_map<uint16_t, int> entrance_usage;
for (auto each_entrance : overworld_.Entrances()) {
if (each_entrance.map_id_ < 0x40 + (current_world_ * 0x40) &&
each_entrance.map_id_ >= (current_world_ * 0x40)) {
entrance_usage[each_entrance.entrance_id_]++;
}
}
}
void OverworldEditor::DrawUsageGrid() {
// Create a grid of 8x8 squares
int totalSquares = 128;
int squaresWide = 8;
int squaresTall = (totalSquares + squaresWide - 1) /
squaresWide; // Ceiling of totalSquares/squaresWide
// Loop through each row
for (int row = 0; row < squaresTall; ++row) {
ImGui::NewLine();
for (int col = 0; col < squaresWide; ++col) {
if (row * squaresWide + col >= totalSquares) {
break;
}
// Determine if this square should be highlighted
bool highlight = selected_usage_map_ == (row * squaresWide + col);
// Set highlight color if needed
if (highlight) {
ImGui::PushStyleColor(
ImGuiCol_Button,
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
}
// Create a button or selectable for each square
if (ImGui::Button("##square", ImVec2(20, 20))) {
// Switch over to the room editor tab
// and add a room tab by the ID of the square
// that was clicked
}
// Reset style if it was highlighted
if (highlight) {
ImGui::PopStyleColor();
}
// Check if the square is hovered
if (ImGui::IsItemHovered()) {
// Display a tooltip with all the room properties
}
// Keep squares in the same line
ImGui::SameLine();
}
}
}
void OverworldEditor::LoadAnimatedMaps() {
int world_index = 0;
static std::vector<bool> animated_built(0x40, false);
if (!animated_built[world_index]) {
animated_maps_[world_index] = maps_bmp_[world_index];
auto &map = *overworld_.mutable_overworld_map(world_index);
map.DrawAnimatedTiles();
map.BuildTileset();
map.BuildTiles16Gfx(overworld_.tiles16().size());
OWBlockset blockset;
if (current_world_ == 0) {
blockset = overworld_.map_tiles().light_world;
} else if (current_world_ == 1) {
blockset = overworld_.map_tiles().dark_world;
} else {
blockset = overworld_.map_tiles().special_world;
}
map.BuildBitmap(blockset);
gui::BuildAndRenderBitmapPipeline(0x200, 0x200, 0x200, map.BitmapData(),
*rom(), animated_maps_[world_index],
*map.mutable_current_palette());
animated_built[world_index] = true;
}
}
// ----------------------------------------------------------------------------
void OverworldEditor::DrawDebugWindow() {
ImGui::Text("Current Map: %d", current_map_);
ImGui::Text("Current Tile16: %d", current_tile16_);

View File

@@ -71,6 +71,9 @@ class OverworldEditor : public Editor,
auto overworld() { return &overworld_; }
int jump_to_tab() { return jump_to_tab_; }
int jump_to_tab_ = -1;
void Shutdown() {
for (auto &bmp : tile16_individual_) {
bmp.Cleanup();
@@ -90,10 +93,6 @@ class OverworldEditor : public Editor,
private:
absl::Status UpdateOverworldEdit();
absl::Status UpdateUsageStats();
void DrawUsageGrid();
void CalculateUsageStats();
absl::Status DrawToolset();
void DrawOverworldMapSettings();
@@ -103,17 +102,16 @@ class OverworldEditor : public Editor,
void RefreshMapPalette();
void RefreshMapProperties();
void DrawOverworldProperties();
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling);
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling,
bool holes = false);
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling);
void DrawOverworldMaps();
void DrawOverworldItems();
void DrawOverworldSprites();
void DrawOverworldMaps();
void DrawOverworldEdits();
void RenderUpdatedMapBitmap(const ImVec2 &click_position,
const Bytes &tile_data);
void CheckForOverworldEdits();
void CheckForCurrentMap();
void DrawOverworldCanvas();
@@ -124,8 +122,16 @@ class OverworldEditor : public Editor,
void DrawTileSelector();
absl::Status LoadSpriteGraphics();
void DrawOverworldProperties();
absl::Status DrawExperimentalModal();
absl::Status UpdateUsageStats();
void DrawUsageGrid();
void CalculateUsageStats();
void LoadAnimatedMaps();
void DrawDebugWindow();
auto gfx_group_editor() const { return gfx_group_editor_; }
@@ -149,7 +155,6 @@ class OverworldEditor : public Editor,
int current_tile16_ = 0;
int selected_tile_ = 0;
int game_state_ = 0;
int current_exit_ = 0;
int selected_entrance_ = 0;
int selected_usage_map_ = 0xFFFF;
@@ -178,10 +183,16 @@ class OverworldEditor : public Editor,
bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false;
bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance,
ImVec2 canvas_p, ImVec2 scrolling);
zelda3::OverworldEntrance *dragged_entrance_;
zelda3::OverworldEntrance *current_entrance_;
bool is_dragging_entity_ = false;
zelda3::OverworldEntity *dragged_entity_;
zelda3::OverworldEntity *current_entity_;
int current_entrance_id_ = 0;
zelda3::OverworldEntrance current_entrance_;
int current_exit_id_ = 0;
zelda3::OverworldExit current_exit_;
int current_item_id_ = 0;
zelda3::OverworldItem current_item_;
bool show_experimental = false;
std::string ow_tilemap_filename_ = "";
@@ -221,6 +232,8 @@ class OverworldEditor : public Editor,
gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
absl::Status status_;
};
} // namespace editor