8#include "absl/strings/ascii.h"
9#include "absl/strings/match.h"
10#include "absl/strings/str_format.h"
15#include "imgui/imgui.h"
27 return absl::StrFormat(
"%02X %s", item_id,
32 int room_id,
size_t index, uint8_t item_id,
bool big_chest) {
33 const auto status = viewer.
EditChest(room_id, index, item_id, big_chest);
34 state.
error = status.ok() ?
"" : std::string(status.message());
40 for (
size_t i = 0; i < objects.size(); ++i) {
54 const bool inline_buttons =
55 ImGui::GetContentRegionAvail().x >=
56 ImGui::CalcTextSize(
"Add small chestAdd big chest").x +
57 ImGui::GetStyle().FramePadding.x * 4 +
58 ImGui::GetStyle().ItemSpacing.x;
59 const float width = inline_buttons ? (ImGui::GetContentRegionAvail().x -
60 ImGui::GetStyle().ItemSpacing.x) /
63 for (
int big = 0; big <= 1; ++big) {
64 if (big && inline_buttons) {
67 if (ImGui::Button(big ?
"Add big chest" :
"Add small chest",
69 const int id = big ? 0xFB1 : 0xF99;
81 "Click in the canvas to place a %s chest. Escape cancels.",
82 preview->id_ == 0xFB1 ?
"big" :
"small");
83 }
else if (!active_room) {
85 "Open this room in the canvas to place or select a chest.");
88 "New chests start with 1 Rupee. Choose a chest below to change its "
95 const auto object_index =
97 ImGui::BeginDisabled(!object_index || viewer.
current_room_id() != room_id);
98 if (ImGui::Button(
"Select in canvas", ImVec2(-1, 0))) {
107 ImGui::EndDisabled();
109 bool deleted =
false;
110 if (ImGui::Button(
"Delete chest", ImVec2(-1, 0))) {
111 const auto status = viewer.
DeleteChest(room_id, index);
112 state.
error = status.ok() ?
"" : std::string(status.message());
113 deleted = status.ok();
116 if (ImGui::IsItemHovered()) {
118 "Remove the chest object and its reward. Undo restores both.");
120 ImGui::EndDisabled();
127 ImGui::TextUnformatted(
"Reward");
128 ImGui::SetNextItemWidth(-1);
129 const bool open = ImGui::BeginCombo(
131 ImGuiComboFlags_HeightLarge);
136 ImGui::SetNextItemWidth(-1);
137 const bool search_changed =
138 ImGui::InputTextWithHint(
"##ChestSearch",
"Search name or hex ID...",
142 const std::string search = absl::AsciiStrToLower(state.
search.data());
145 if (ImGui::BeginChild(
"##ChestRewardChoices",
146 ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 8))) {
147 if (search_changed) {
148 ImGui::SetScrollY(0);
152 bool has_matches =
false;
153 for (
int id = 0;
id <= 0xFF; ++id) {
156 if (
id >=
static_cast<int>(
160 static_cast<uint8_t
>(
id), labels)) !=
"project") {
163 const std::string label =
165 if (!absl::StrContains(absl::AsciiStrToLower(label), search)) {
169 if (ImGui::Selectable(label.c_str(),
chest.id ==
id)) {
170 ApplyEdit(viewer, state, room_id, index,
static_cast<uint8_t
>(
id),
174 ImGui::CloseCurrentPopup();
177 absl::StrFormat(
"ChestReward%02X",
id));
178 if (
chest.id ==
id && search.empty()) {
179 ImGui::SetItemDefaultFocus();
183 ImGui::TextDisabled(
"No matching rewards");
204 ImGui::TextUnformatted(
"Chests");
208 "Open this room in the canvas to load its chest contents.");
212 if (state.room_id != room_id) {
217 ImGui::PushID(
"DungeonChestEditor");
218 ImGui::PushID(room_id);
220 size_t chest_objects = 0;
224 const auto mapping_status =
226 const bool has_mapping = mapping_status.ok();
227 ImGui::TextWrapped(
"%zu contents records · %zu chest objects", chests.size(),
231 "Chest objects and contents do not match in room order. Rewards remain "
232 "editable; adding, deleting, and changing chest type require a "
234 "object for each record.");
235 ImGui::TextWrapped(
"%s", std::string(mapping_status.message()).c_str());
238 ImGui::TextWrapped(
"This room exceeds the standard six-chest limit.");
240 DrawChestPlacement(room_id, has_mapping, viewer, state);
241 if (chests.empty()) {
242 ImGui::TextWrapped(
"No chest contents records in this room.");
243 state.selected_index = 0;
244 state.last_canvas_selection.reset();
251 const auto selection =
253 const auto canvas_selection = selection.size() == 1
254 ? std::optional<size_t>(selection.front())
256 if (canvas_selection != state.last_canvas_selection) {
257 state.last_canvas_selection = canvas_selection;
258 if (has_mapping && canvas_selection) {
262 state.selected_index =
static_cast<int>(*ordinal);
263 state.search.fill(0);
269 state.selected_index =
270 std::clamp(state.selected_index, 0,
static_cast<int>(chests.size()) - 1);
271 ImGui::SetNextItemWidth(-1);
272 const std::string current = absl::StrFormat(
273 "Chest %d · %s", state.selected_index + 1,
274 ItemPreview(chests[state.selected_index].id, viewer.
project()));
275 const bool open = ImGui::BeginCombo(
"##ChestRecord", current.c_str());
278 for (
size_t i = 0; i < chests.size(); ++i) {
279 const std::string label = absl::StrFormat(
280 "Chest %zu · %s", i + 1, ItemPreview(chests[i].
id, viewer.
project()));
281 if (ImGui::Selectable(label.c_str(), state.selected_index == i)) {
282 state.selected_index =
static_cast<int>(i);
283 state.search.fill(0);
287 absl::StrFormat(
"ChestRecord%zu", i));
291 const size_t index =
static_cast<size_t>(state.selected_index);
292 if (DrawChestActions(room_id, index, has_mapping, room, viewer, state)) {
293 state.search.fill(0);
298 ImGui::PushID(state.selected_index);
301 const auto chest = chests[index];
303 DrawItemChoice(room_id, index,
chest, viewer, state);
305 ImGui::BeginDisabled(!has_mapping);
306 if (ImGui::Checkbox(
"Big chest", &big)) {
307 ApplyEdit(viewer, state, room_id, index, room.
GetChests()[index].id, big);
310 ImGui::EndDisabled();
311 if (ImGui::TreeNode(
"Advanced##ChestAdvanced")) {
312 int item_id = room.
GetChests()[index].id;
313 ImGui::SetNextItemWidth(-1);
316 "%02X", ImGuiInputTextFlags_CharsHexadecimal,
317 {
reinterpret_cast<uintptr_t
>(&viewer),
318 static_cast<uint64_t
>(room_id), index});
321 if (item_id < 0 || item_id > 0xFF) {
322 state.error =
"Item ID must fit one byte (00-FF).";
324 ApplyEdit(viewer, state, room_id, index,
static_cast<uint8_t
>(item_id),
329 "Raw item ID (hex). Unknown hack-specific values are kept.");
332 ImGui::EndDisabled();
333 if (!state.error.empty()) {
334 ImGui::TextWrapped(
"Edit not applied: %s", state.error.c_str());
DungeonRoomStore * rooms() const
DungeonChestEditorState & chest_editor_state()
int current_room_id() const
DungeonObjectInteraction & object_interaction()
void TriggerCanvasPingRect(int pixel_x, int pixel_y, int pixel_w, int pixel_h)
absl::Status EditChest(int room_id, size_t index, uint8_t item_id, bool big_chest)
void SetPreviewObject(const zelda3::RoomObject &object)
bool header_read_only() const
void SetObjectInteractionEnabled(bool enabled)
absl::Status DeleteChest(int room_id, size_t index)
const project::YazeProject * project() const
void ScrollToTile(int tile_x, int tile_y)
void SetSelectedObjects(const std::vector< size_t > &indices)
const zelda3::RoomObject * GetPlacementPreview() const
std::vector< size_t > GetSelectedObjectIndices() const
zelda3::Room * GetIfLoaded(int room_id)
const std::vector< chest_data > & GetChests() const
const std::vector< RoomObject > & GetTileObjects() const
bool AreChestsLoaded() const
void DrawChestPlacement(int room_id, bool has_mapping, DungeonCanvasViewer &viewer, DungeonChestEditorState &state)
std::string ItemPreview(uint8_t item_id, const project::YazeProject *project)
std::optional< size_t > FindChestObject(const zelda3::Room &room, size_t ordinal)
void ApplyEdit(DungeonCanvasViewer &viewer, DungeonChestEditorState &state, int room_id, size_t index, uint8_t item_id, bool big_chest)
void DrawItemChoice(int room_id, size_t index, const chest_data chest, DungeonCanvasViewer &viewer, DungeonChestEditorState &state)
bool DrawChestActions(int room_id, size_t index, bool has_mapping, zelda3::Room &room, DungeonCanvasViewer &viewer, DungeonChestEditorState &state)
Editors are the view controllers for the application.
std::string GetDungeonChestItemLabel(uint8_t item_id)
void DrawDungeonChestEditor(int room_id, zelda3::Room &room, DungeonCanvasViewer &viewer)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
absl::Status ValidateChestObjectMapping(const std::vector< RoomObject > &objects, const std::vector< chest_data > &chests)
const char * GetItemReceiptLabelSource(uint8_t id, const ResourceLabelProvider::ProjectLabels *project_labels)
std::optional< size_t > ChestIndexForObject(const std::vector< RoomObject > &objects, size_t object_index)
std::string GetItemReceiptLabel(uint8_t id, const ResourceLabelProvider::ProjectLabels *project_labels)
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
constexpr bool IsStatefulChestObjectId(int object_id)
constexpr size_t kMaxChests
Legacy chest data structure.
std::array< char, 64 > search
Modern project structure with comprehensive settings consolidation.
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
static const std::vector< std::string > & GetItemReceiptNames()