yaze 0.2.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
entity.cc
Go to the documentation of this file.
2
3#include "app/gui/icons.h"
4#include "app/gui/input.h"
5#include "app/gui/style.h"
6#include "util/hex.h"
7
8namespace yaze {
9namespace editor {
10
11using ImGui::BeginChild;
12using ImGui::Button;
13using ImGui::Checkbox;
14using ImGui::EndChild;
15using ImGui::SameLine;
16using ImGui::Selectable;
17using ImGui::Text;
18
19constexpr float kInputFieldSize = 30.f;
20
22 ImVec2 canvas_p0, ImVec2 scrolling) {
23 // Get the mouse position relative to the canvas
24 const ImGuiIO &io = ImGui::GetIO();
25 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
26 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
27
28 // Check if the mouse is hovering over the entity
29 if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
30 mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) {
31 return true;
32 }
33 return false;
34}
35
36void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0,
37 ImVec2 scrolling, bool free_movement) {
38 // Get the mouse position relative to the canvas
39 const ImGuiIO &io = ImGui::GetIO();
40 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
41 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
42
43 // Calculate the new position on the 16x16 grid
44 int new_x = static_cast<int>(mouse_pos.x) / 16 * 16;
45 int new_y = static_cast<int>(mouse_pos.y) / 16 * 16;
46 if (free_movement) {
47 new_x = static_cast<int>(mouse_pos.x) / 8 * 8;
48 new_y = static_cast<int>(mouse_pos.y) / 8 * 8;
49 }
50
51 // Update the entity position
52 entity->set_x(new_x);
53 entity->set_y(new_y);
54}
55
56void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
57 ImVec2 scrolling, bool &is_dragging_entity,
58 zelda3::GameEntity *&dragged_entity,
59 zelda3::GameEntity *&current_entity,
60 bool free_movement) {
61 std::string entity_type = "Entity";
63 entity_type = "Exit";
64 } else if (entity->entity_type_ ==
66 entity_type = "Entrance";
68 entity_type = "Sprite";
70 entity_type = "Item";
71 }
72 const auto is_hovering =
73 IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling);
74
75 const auto drag_or_clicked = ImGui::IsMouseDragging(ImGuiMouseButton_Left) ||
76 ImGui::IsMouseClicked(ImGuiMouseButton_Left);
77
78 if (is_hovering && drag_or_clicked && !is_dragging_entity) {
79 dragged_entity = entity;
80 is_dragging_entity = true;
81 } else if (is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
82 current_entity = entity;
83 ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str());
84 } else if (is_dragging_entity && dragged_entity == entity &&
85 ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
86 MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
87 entity->UpdateMapProperties(entity->map_id_);
88 is_dragging_entity = false;
89 dragged_entity = nullptr;
90 } else if (is_dragging_entity && dragged_entity == entity) {
91 if (ImGui::BeginDragDropSource()) {
92 ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
93 sizeof(zelda3::GameEntity));
94 Text("Moving %s ID: %s", entity_type.c_str(),
95 util::HexByte(entity->entity_id_).c_str());
96 ImGui::EndDragDropSource();
97 }
98 MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
99 entity->x_ = dragged_entity->x_;
100 entity->y_ = dragged_entity->y_;
101 entity->UpdateMapProperties(entity->map_id_);
102 }
103}
104
106 bool set_done = false;
107 if (set_done) {
108 set_done = false;
109 }
110 if (ImGui::BeginPopup("Entrance Inserter")) {
111 static int entrance_id = 0;
112 gui::InputHex("Entrance ID", &entrance_id);
113
114 if (Button(ICON_MD_DONE)) {
115 set_done = true;
116 ImGui::CloseCurrentPopup();
117 }
118
119 SameLine();
120 if (Button(ICON_MD_CANCEL)) {
121 ImGui::CloseCurrentPopup();
122 }
123
124 ImGui::EndPopup();
125 }
126 return set_done;
127}
128
129// TODO: Implement deleting OverworldEntrance objects, currently only hides them
131 static bool set_done = false;
132 if (set_done) {
133 set_done = false;
134 }
135 if (ImGui::BeginPopupModal("Entrance editor", NULL,
136 ImGuiWindowFlags_AlwaysAutoResize)) {
137 gui::InputHexWord("Map ID", &entrance.map_id_);
138 gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
139 kInputFieldSize + 20);
140 gui::InputHex("X", &entrance.x_);
141 gui::InputHex("Y", &entrance.y_);
142
143 if (Button(ICON_MD_DONE)) {
144 ImGui::CloseCurrentPopup();
145 }
146 SameLine();
147 if (Button(ICON_MD_CANCEL)) {
148 set_done = true;
149 ImGui::CloseCurrentPopup();
150 }
151 SameLine();
152 if (Button(ICON_MD_DELETE)) {
153 entrance.deleted = true;
154 ImGui::CloseCurrentPopup();
155 }
156 ImGui::EndPopup();
157 }
158 return set_done;
159}
160
161// TODO: Implement deleting OverworldExit objects
163 if (ImGui::BeginPopup("Exit Inserter")) {
164 static int exit_id = 0;
165 gui::InputHex("Exit ID", &exit_id);
166
167 if (Button(ICON_MD_DONE)) {
168 ImGui::CloseCurrentPopup();
169 }
170
171 SameLine();
172 if (Button(ICON_MD_CANCEL)) {
173 ImGui::CloseCurrentPopup();
174 }
175
176 ImGui::EndPopup();
177 }
178}
179
181 static bool set_done = false;
182 if (set_done) {
183 set_done = false;
184 }
185 if (ImGui::BeginPopupModal("Exit editor", NULL,
186 ImGuiWindowFlags_AlwaysAutoResize)) {
187 // Normal door: None = 0, Wooden = 1, Bombable = 2
188 static int doorType = exit.door_type_1_;
189 // Fancy door: None = 0, Sanctuary = 1, Palace = 2
190 static int fancyDoorType = exit.door_type_2_;
191
192 static int xPos = 0;
193 static int yPos = 0;
194
195 // Special overworld exit properties
196 static int centerY = 0;
197 static int centerX = 0;
198 static int unk1 = 0;
199 static int unk2 = 0;
200 static int linkPosture = 0;
201 static int spriteGFX = 0;
202 static int bgGFX = 0;
203 static int palette = 0;
204 static int sprPal = 0;
205 static int top = 0;
206 static int bottom = 0;
207 static int left = 0;
208 static int right = 0;
209 static int leftEdgeOfMap = 0;
210
211 gui::InputHexWord("Room", &exit.room_id_);
212 SameLine();
213 gui::InputHex("Entity ID", &exit.entity_id_, 4);
214 gui::InputHexWord("Map", &exit.map_id_);
215 SameLine();
216 Checkbox("Automatic", &exit.is_automatic_);
217
218 gui::InputHex("X Positon", &exit.x_);
219 SameLine();
220 gui::InputHex("Y Position", &exit.y_);
221
222 gui::InputHexByte("X Camera", &exit.x_camera_);
223 SameLine();
224 gui::InputHexByte("Y Camera", &exit.y_camera_);
225
226 gui::InputHexWord("X Scroll", &exit.x_scroll_);
227 SameLine();
228 gui::InputHexWord("Y Scroll", &exit.y_scroll_);
229
230 ImGui::Separator();
231
232 static bool show_properties = false;
233 Checkbox("Show properties", &show_properties);
234 if (show_properties) {
235 Text("Deleted? %s", exit.deleted_ ? "true" : "false");
236 Text("Hole? %s", exit.is_hole_ ? "true" : "false");
237 Text("Large Map? %s", exit.large_map_ ? "true" : "false");
238 }
239
240 gui::TextWithSeparators("Unimplemented below");
241
242 ImGui::RadioButton("None", &doorType, 0);
243 SameLine();
244 ImGui::RadioButton("Wooden", &doorType, 1);
245 SameLine();
246 ImGui::RadioButton("Bombable", &doorType, 2);
247 // If door type is not None, input positions
248 if (doorType != 0) {
249 gui::InputHex("Door X pos", &xPos);
250 gui::InputHex("Door Y pos", &yPos);
251 }
252
253 ImGui::RadioButton("None##Fancy", &fancyDoorType, 0);
254 SameLine();
255 ImGui::RadioButton("Sanctuary", &fancyDoorType, 1);
256 SameLine();
257 ImGui::RadioButton("Palace", &fancyDoorType, 2);
258 // If fancy door type is not None, input positions
259 if (fancyDoorType != 0) {
260 // Placeholder for fancy door's X position
261 gui::InputHex("Fancy Door X pos", &xPos);
262 // Placeholder for fancy door's Y position
263 gui::InputHex("Fancy Door Y pos", &yPos);
264 }
265
266 static bool special_exit = false;
267 Checkbox("Special exit", &special_exit);
268 if (special_exit) {
269 gui::InputHex("Center X", &centerX);
270
271 gui::InputHex("Center Y", &centerY);
272 gui::InputHex("Unk1", &unk1);
273 gui::InputHex("Unk2", &unk2);
274
275 gui::InputHex("Link's posture", &linkPosture);
276 gui::InputHex("Sprite GFX", &spriteGFX);
277 gui::InputHex("BG GFX", &bgGFX);
278 gui::InputHex("Palette", &palette);
279 gui::InputHex("Spr Pal", &sprPal);
280
281 gui::InputHex("Top", &top);
282 gui::InputHex("Bottom", &bottom);
283 gui::InputHex("Left", &left);
284 gui::InputHex("Right", &right);
285
286 gui::InputHex("Left edge of map", &leftEdgeOfMap);
287 }
288
289 if (Button(ICON_MD_DONE)) {
290 ImGui::CloseCurrentPopup();
291 }
292
293 SameLine();
294
295 if (Button(ICON_MD_CANCEL)) {
296 set_done = true;
297 ImGui::CloseCurrentPopup();
298 }
299
300 SameLine();
301 if (Button(ICON_MD_DELETE)) {
302 exit.deleted_ = true;
303 ImGui::CloseCurrentPopup();
304 }
305
306 ImGui::EndPopup();
307 }
308
309 return set_done;
310}
311
313 // Contents of the Context Menu
314 if (ImGui::BeginPopup("Item Inserter")) {
315 static size_t new_item_id = 0;
316 Text("Add Item");
317 BeginChild("ScrollRegion", ImVec2(150, 150), true,
318 ImGuiWindowFlags_AlwaysVerticalScrollbar);
319 for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
320 if (Selectable(zelda3::kSecretItemNames[i].c_str(), i == new_item_id)) {
321 new_item_id = i;
322 }
323 }
324 EndChild();
325
326 if (Button(ICON_MD_DONE)) {
327 // Add the new item to the overworld
328 new_item_id = 0;
329 ImGui::CloseCurrentPopup();
330 }
331 SameLine();
332
333 if (Button(ICON_MD_CANCEL)) {
334 ImGui::CloseCurrentPopup();
335 }
336
337 ImGui::EndPopup();
338 }
339}
340
341// TODO: Implement deleting OverworldItem objects, currently only hides them
343 static bool set_done = false;
344 if (set_done) {
345 set_done = false;
346 }
347 if (ImGui::BeginPopupModal("Item editor", NULL,
348 ImGuiWindowFlags_AlwaysAutoResize)) {
349 BeginChild("ScrollRegion", ImVec2(150, 150), true,
350 ImGuiWindowFlags_AlwaysVerticalScrollbar);
351 ImGui::BeginGroup();
352 for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
353 if (Selectable(zelda3::kSecretItemNames[i].c_str(), item.id_ == i)) {
354 item.id_ = i;
355 }
356 }
357 ImGui::EndGroup();
358 EndChild();
359
360 if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
361 SameLine();
362 if (Button(ICON_MD_CLOSE)) {
363 set_done = true;
364 ImGui::CloseCurrentPopup();
365 }
366 SameLine();
367 if (Button(ICON_MD_DELETE)) {
368 item.deleted = true;
369 ImGui::CloseCurrentPopup();
370 }
371
372 ImGui::EndPopup();
373 }
374 return set_done;
375}
376
377const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr;
378
379void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
380 static ImGuiTextFilter filter;
381 static int selected_id = 0;
382 static std::vector<SpriteItem> items;
383
384 // Initialize items if empty
385 if (items.empty()) {
386 for (int i = 0; i < 256; ++i) {
387 items.push_back(SpriteItem{i, zelda3::kSpriteDefaultNames[i].data()});
388 }
389 }
390
391 filter.Draw("Filter", 180);
392
393 if (ImGui::BeginTable("##sprites", 2,
394 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
395 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
397 ImGui::TableSetupColumn("Name", 0, 0.0f, MyItemColumnID_Name);
398 ImGui::TableHeadersRow();
399
400 // Handle sorting
401 if (ImGuiTableSortSpecs *sort_specs = ImGui::TableGetSortSpecs()) {
402 if (sort_specs->SpecsDirty) {
403 SpriteItem::SortWithSortSpecs(sort_specs, items);
404 sort_specs->SpecsDirty = false;
405 }
406 }
407
408 // Display filtered and sorted items
409 for (const auto &item : items) {
410 if (filter.PassFilter(item.name)) {
411 ImGui::TableNextRow();
412 ImGui::TableSetColumnIndex(0);
413 Text("%d", item.id);
414 ImGui::TableSetColumnIndex(1);
415
416 if (Selectable(item.name, selected_id == item.id,
417 ImGuiSelectableFlags_SpanAllColumns)) {
418 selected_id = item.id;
419 onSpriteSelect(item.id);
420 }
421 }
422 }
423 ImGui::EndTable();
424 }
425}
426
427// TODO: Implement deleting OverworldSprite objects
429 if (ImGui::BeginPopup("Sprite Inserter")) {
430 static int new_sprite_id = 0;
431 Text("Add Sprite");
432 BeginChild("ScrollRegion", ImVec2(250, 250), true,
433 ImGuiWindowFlags_AlwaysVerticalScrollbar);
434 DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; });
435 EndChild();
436
437 if (Button(ICON_MD_DONE)) {
438 // Add the new item to the overworld
439 new_sprite_id = 0;
440 ImGui::CloseCurrentPopup();
441 }
442 SameLine();
443
444 if (Button(ICON_MD_CANCEL)) {
445 ImGui::CloseCurrentPopup();
446 }
447
448 ImGui::EndPopup();
449 }
450}
451
453 static bool set_done = false;
454 if (set_done) {
455 set_done = false;
456 }
457 if (ImGui::BeginPopupModal("Sprite editor", NULL,
458 ImGuiWindowFlags_AlwaysAutoResize)) {
459 BeginChild("ScrollRegion", ImVec2(350, 350), true,
460 ImGuiWindowFlags_AlwaysVerticalScrollbar);
461 ImGui::BeginGroup();
462 Text("%s", sprite.name().c_str());
463
464 DrawSpriteTable([&sprite](int selected_id) {
465 sprite.set_id(selected_id);
466 sprite.UpdateMapProperties(sprite.map_id());
467 });
468 ImGui::EndGroup();
469 EndChild();
470
471 if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
472 SameLine();
473 if (Button(ICON_MD_CLOSE)) {
474 set_done = true;
475 ImGui::CloseCurrentPopup();
476 }
477 SameLine();
478 if (Button(ICON_MD_DELETE)) {
479 sprite.set_deleted(true);
480 ImGui::CloseCurrentPopup();
481 }
482
483 ImGui::EndPopup();
484 }
485 return set_done;
486}
487
488} // namespace editor
489} // namespace yaze
Base class for all overworld and dungeon entities.
Definition common.h:20
enum yaze::zelda3::GameEntity::EntityType entity_type_
virtual void UpdateMapProperties(uint16_t map_id)=0
auto set_x(int x)
Definition common.h:40
auto set_y(int y)
Definition common.h:41
A class for managing sprites in the overworld and underworld.
Definition sprite.h:279
auto map_id() const
Definition sprite.h:344
auto set_id(uint8_t id)
Definition sprite.h:339
void UpdateMapProperties(uint16_t map_id) override
Definition sprite.cc:9
auto set_deleted(bool deleted)
Definition sprite.h:356
#define ICON_MD_CANCEL
Definition icons.h:362
#define ICON_MD_DONE
Definition icons.h:605
#define ICON_MD_DELETE
Definition icons.h:528
#define ICON_MD_CLOSE
Definition icons.h:416
Editors are the view controllers for the application.
void DrawExitInserterPopup()
Definition entity.cc:162
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement)
Definition entity.cc:36
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:452
void DrawItemInsertPopup()
Definition entity.cc:312
bool DrawEntranceInserterPopup()
Definition entity.cc:105
void DrawSpriteInserterPopup()
Definition entity.cc:428
void DrawSpriteTable(std::function< void(int)> onSpriteSelect)
Definition entity.cc:379
void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool &is_dragging_entity, zelda3::GameEntity *&dragged_entity, zelda3::GameEntity *&current_entity, bool free_movement)
Definition entity.cc:56
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:130
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, ImVec2 canvas_p0, ImVec2 scrolling)
Definition entity.cc:21
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:342
constexpr float kInputFieldSize
Definition entity.cc:19
@ MyItemColumnID_ID
Definition entity.h:38
@ MyItemColumnID_Name
Definition entity.h:39
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:180
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:153
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:134
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:167
void TextWithSeparators(const absl::string_view &text)
Definition style.cc:746
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:33
const std::vector< std::string > kSecretItemNames
Main namespace for the application.
Definition controller.cc:18
static const ImGuiTableSortSpecs * s_current_sort_specs
Definition entity.h:48
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, std::vector< SpriteItem > &items)
Definition entity.h:50