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