110 static const char* kCategories[] = {
"All",
"Enemies",
"NPCs",
"Bosses",
112 ImGui::SetNextItemWidth(100);
114 IM_ARRAYSIZE(kCategories));
118 ImGui::SetNextItemWidth(120);
123 float available_height = ImGui::GetContentRegionAvail().y;
125 float reserved_height = 120.0f;
128 std::max(150.0f, std::min(400.0f, available_height - reserved_height));
131 float panel_width = ImGui::GetContentRegionAvail().x;
133 std::max(28.0f, std::min(40.0f, (panel_width - 40.0f) / 8.0f));
135 std::max(1,
static_cast<int>(panel_width / (sprite_size + 6)));
137 ImGui::BeginChild(
"##SpriteGrid", ImVec2(0, grid_height),
true,
138 ImGuiWindowFlags_HorizontalScrollbar);
141 for (
int i = 0; i < 256; ++i) {
153 button_color.x = std::min(1.0f, button_color.x + 0.2f);
154 button_color.y = std::min(1.0f, button_color.y + 0.2f);
155 button_color.z = std::min(1.0f, button_color.z + 0.2f);
160 {{ImGuiCol_Button, button_color},
161 {ImGuiCol_ButtonHovered,
162 ImVec4(std::min(1.0f, button_color.x + 0.1f),
163 std::min(1.0f, button_color.y + 0.1f),
164 std::min(1.0f, button_color.z + 0.1f), 1.0f)},
165 {ImGuiCol_ButtonActive,
166 ImVec4(std::min(1.0f, button_color.x + 0.2f),
167 std::min(1.0f, button_color.y + 0.2f),
168 std::min(1.0f, button_color.z + 0.2f), 1.0f)}});
172 std::string label = absl::StrFormat(
"%s\n%02X", icon, i);
173 if (ImGui::Button(label.c_str(), ImVec2(sprite_size, sprite_size))) {
183 if (ImGui::IsItemHovered()) {
185 ImGui::SetTooltip(
"%s (0x%02X)\n[%s]\nClick to select for placement",
191 ImVec2 min = ImGui::GetItemRectMin();
192 ImVec2 max = ImGui::GetItemRectMax();
194 ImGui::ColorConvertFloat4ToU32(theme.dungeon_selection_primary);
195 ImGui::GetWindowDrawList()->AddRect(min, max, sel_color, 0.0f, 0, 2.0f);
201 if (col < items_per_row) {
214 auto& sprites = room.GetSprites();
217 int sprite_count =
static_cast<int>(sprites.size());
219 sprite_count > 16 ? theme.text_error_red : theme.text_primary;
220 ImGui::TextColored(count_color,
ICON_MD_LIST " Room Sprites: %d/16",
223 if (sprite_count > 16) {
226 if (ImGui::IsItemHovered()) {
228 "Room exceeds sprite limit (16 max)!\n"
229 "This may cause game crashes.");
233 if (sprites.empty()) {
234 ImGui::TextColored(theme.text_secondary_gray,
240 float available = ImGui::GetContentRegionAvail().y;
241 float list_height = std::max(100.0f, available * 0.4f);
243 ImGui::BeginChild(
"##SpriteList", ImVec2(0, list_height),
true);
244 for (
size_t i = 0; i < sprites.size(); ++i) {
245 const auto& sprite = sprites[i];
248 ImGui::PushID(
static_cast<int>(i));
251 std::string label = absl::StrFormat(
255 if (sprite.key_drop() == 1) {
257 }
else if (sprite.key_drop() == 2) {
262 if (sprite.IsOverlord()) {
266 if (ImGui::Selectable(label.c_str(), is_selected)) {
271 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 80);
272 ImGui::TextColored(theme.text_secondary_gray,
"(%d,%d) L%d", sprite.x(),
273 sprite.y(), sprite.layer());
286 auto& sprites = room.GetSprites();
290 ImGui::TextColored(theme.text_secondary_gray,
301 if (sprite.IsOverlord()) {
303 ImGui::TextColored(theme.status_warning,
ICON_MD_STAR " OVERLORD");
304 if (ImGui::IsItemHovered()) {
306 "This is an Overlord sprite.\n"
307 "Overlords have separate limits (8 max).");
312 ImGui::Text(
"ID: 0x%02X - %s", sprite.id(),
316 int pos_x = sprite.x();
317 int pos_y = sprite.y();
318 ImGui::SetNextItemWidth(60);
319 if (ImGui::InputInt(
"X##SpriteX", &pos_x, 1, 8)) {
320 pos_x = std::clamp(pos_x, 0, 63);
324 ImGui::SetNextItemWidth(60);
325 if (ImGui::InputInt(
"Y##SpriteY", &pos_y, 1, 8)) {
326 pos_y = std::clamp(pos_y, 0, 63);
331 int subtype = sprite.subtype();
332 ImGui::SetNextItemWidth(80);
333 if (ImGui::Combo(
"Subtype##SpriteSubtype", &subtype,
334 "0\0001\0002\0003\0004\0005\0006\0007\0")) {
335 sprite.set_subtype(subtype);
337 if (ImGui::IsItemHovered()) {
339 "Controls sprite behavior variant.\n"
340 "Effect varies by sprite type.");
344 int layer = sprite.layer();
345 ImGui::SetNextItemWidth(80);
346 if (ImGui::Combo(
"Layer##SpriteLayer", &layer,
347 "Upper (0)\0Lower (1)\0Both (2)\0")) {
348 sprite.set_layer(layer);
350 if (ImGui::IsItemHovered()) {
352 "Which layer the sprite appears on.\n"
353 "Upper = main floor, Lower = basement.");
357 int key_drop = sprite.key_drop();
358 ImGui::Text(
"Key Drop:");
360 if (ImGui::RadioButton(
"None##KeyNone", key_drop == 0)) {
361 sprite.set_key_drop(0);
364 if (ImGui::RadioButton(
ICON_MD_KEY " Small##KeySmall", key_drop == 1)) {
365 sprite.set_key_drop(1);
368 if (ImGui::RadioButton(
ICON_MD_VPN_KEY " Big##KeyBig", key_drop == 2)) {
369 sprite.set_key_drop(2);
371 if (ImGui::IsItemHovered()) {
372 ImGui::SetTooltip(
"Key dropped when sprite is defeated.");
388 sprites.push_back(copy);
396 bool is_enemy = (sprite_id >= 0x09 && sprite_id <= 0x7F);
397 bool is_npc = (sprite_id >= 0x80 && sprite_id <= 0xBF);
398 bool is_boss = (sprite_id >= 0xC0 && sprite_id <= 0xD8);
399 bool is_item = (sprite_id >= 0xD9 && sprite_id <= 0xFF);
415 std::string name_lower = name;
417 for (
auto& c : name_lower)
418 c =
static_cast<char>(tolower(c));
419 for (
auto& c : filter_lower)
420 c =
static_cast<char>(tolower(c));
421 if (name_lower.find(filter_lower) == std::string::npos) {