112 static const char* kCategories[] = {
113 "All",
"Enemies",
"NPCs",
"Bosses",
"Items"
115 ImGui::SetNextItemWidth(100);
116 ImGui::Combo(
"##Category", &
selected_category_, kCategories, IM_ARRAYSIZE(kCategories));
120 ImGui::SetNextItemWidth(120);
124 float available_height = ImGui::GetContentRegionAvail().y;
126 float reserved_height = 120.0f;
128 float grid_height = std::max(150.0f, std::min(400.0f, available_height - reserved_height));
131 float panel_width = ImGui::GetContentRegionAvail().x;
132 float sprite_size = std::max(28.0f, std::min(40.0f, (panel_width - 40.0f) / 8.0f));
133 int items_per_row = std::max(1,
static_cast<int>(panel_width / (sprite_size + 6)));
135 ImGui::BeginChild(
"##SpriteGrid", ImVec2(0, grid_height),
true,
136 ImGuiWindowFlags_HorizontalScrollbar);
139 for (
int i = 0; i < 256; ++i) {
150 button_color.x = std::min(1.0f, button_color.x + 0.2f);
151 button_color.y = std::min(1.0f, button_color.y + 0.2f);
152 button_color.z = std::min(1.0f, button_color.z + 0.2f);
157 {{ImGuiCol_Button, button_color},
158 {ImGuiCol_ButtonHovered,
159 ImVec4(std::min(1.0f, button_color.x + 0.1f),
160 std::min(1.0f, button_color.y + 0.1f),
161 std::min(1.0f, button_color.z + 0.1f), 1.0f)},
162 {ImGuiCol_ButtonActive,
163 ImVec4(std::min(1.0f, button_color.x + 0.2f),
164 std::min(1.0f, button_color.y + 0.2f),
165 std::min(1.0f, button_color.z + 0.2f), 1.0f)}});
169 std::string label = absl::StrFormat(
"%s\n%02X", icon, i);
170 if (ImGui::Button(label.c_str(), ImVec2(sprite_size, sprite_size))) {
180 if (ImGui::IsItemHovered()) {
182 ImGui::SetTooltip(
"%s (0x%02X)\n[%s]\nClick to select for placement",
188 ImVec2 min = ImGui::GetItemRectMin();
189 ImVec2 max = ImGui::GetItemRectMax();
190 ImU32 sel_color = ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
191 ImGui::GetWindowDrawList()->AddRect(min, max, sel_color, 0.0f, 0, 2.0f);
197 if (col < items_per_row) {
210 auto& sprites = room.GetSprites();
213 int sprite_count =
static_cast<int>(sprites.size());
215 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
216 ImGui::TextColored(count_color,
ICON_MD_LIST " Room Sprites: %d/16",
219 if (sprite_count > 16) {
222 if (ImGui::IsItemHovered()) {
223 ImGui::SetTooltip(
"Room exceeds sprite limit (16 max)!\n"
224 "This may cause game crashes.");
228 if (sprites.empty()) {
229 ImGui::TextColored(theme.text_secondary_gray,
235 float available = ImGui::GetContentRegionAvail().y;
236 float list_height = std::max(100.0f, available * 0.4f);
238 ImGui::BeginChild(
"##SpriteList", ImVec2(0, list_height),
true);
239 for (
size_t i = 0; i < sprites.size(); ++i) {
240 const auto& sprite = sprites[i];
243 ImGui::PushID(
static_cast<int>(i));
246 std::string label = absl::StrFormat(
"[%02X] %s",
250 if (sprite.key_drop() == 1) {
252 }
else if (sprite.key_drop() == 2) {
257 if (sprite.IsOverlord()) {
261 if (ImGui::Selectable(label.c_str(), is_selected)) {
266 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
267 ImGui::TextColored(theme.text_secondary_gray,
268 "(%d,%d) L%d", sprite.x(), sprite.y(), sprite.layer());
281 auto& sprites = room.GetSprites();
285 ImGui::TextColored(theme.text_secondary_gray,
296 if (sprite.IsOverlord()) {
298 ImGui::TextColored(theme.status_warning,
300 if (ImGui::IsItemHovered()) {
301 ImGui::SetTooltip(
"This is an Overlord sprite.\n"
302 "Overlords have separate limits (8 max).");
307 ImGui::Text(
"ID: 0x%02X - %s", sprite.id(),
311 int pos_x = sprite.x();
312 int pos_y = sprite.y();
313 ImGui::SetNextItemWidth(60);
314 if (ImGui::InputInt(
"X##SpriteX", &pos_x, 1, 8)) {
315 pos_x = std::clamp(pos_x, 0, 63);
319 ImGui::SetNextItemWidth(60);
320 if (ImGui::InputInt(
"Y##SpriteY", &pos_y, 1, 8)) {
321 pos_y = std::clamp(pos_y, 0, 63);
326 int subtype = sprite.subtype();
327 ImGui::SetNextItemWidth(80);
328 if (ImGui::Combo(
"Subtype##SpriteSubtype", &subtype,
329 "0\0001\0002\0003\0004\0005\0006\0007\0")) {
330 sprite.set_subtype(subtype);
332 if (ImGui::IsItemHovered()) {
333 ImGui::SetTooltip(
"Controls sprite behavior variant.\n"
334 "Effect varies by sprite type.");
338 int layer = sprite.layer();
339 ImGui::SetNextItemWidth(80);
340 if (ImGui::Combo(
"Layer##SpriteLayer", &layer,
341 "Upper (0)\0Lower (1)\0Both (2)\0")) {
342 sprite.set_layer(layer);
344 if (ImGui::IsItemHovered()) {
345 ImGui::SetTooltip(
"Which layer the sprite appears on.\n"
346 "Upper = main floor, Lower = basement.");
350 int key_drop = sprite.key_drop();
351 ImGui::Text(
"Key Drop:");
353 if (ImGui::RadioButton(
"None##KeyNone", key_drop == 0)) {
354 sprite.set_key_drop(0);
357 if (ImGui::RadioButton(
ICON_MD_KEY " Small##KeySmall", key_drop == 1)) {
358 sprite.set_key_drop(1);
361 if (ImGui::RadioButton(
ICON_MD_VPN_KEY " Big##KeyBig", key_drop == 2)) {
362 sprite.set_key_drop(2);
364 if (ImGui::IsItemHovered()) {
365 ImGui::SetTooltip(
"Key dropped when sprite is defeated.");
381 sprites.push_back(copy);
389 bool is_enemy = (sprite_id >= 0x09 && sprite_id <= 0x7F);
390 bool is_npc = (sprite_id >= 0x80 && sprite_id <= 0xBF);
391 bool is_boss = (sprite_id >= 0xC0 && sprite_id <= 0xD8);
392 bool is_item = (sprite_id >= 0xD9 && sprite_id <= 0xFF);
404 std::string name_lower = name;
406 for (
auto& c : name_lower) c =
static_cast<char>(tolower(c));
407 for (
auto& c : filter_lower) c =
static_cast<char>(tolower(c));
408 if (name_lower.find(filter_lower) == std::string::npos) {