#include "LevelDebugger.h" #include "Constants.h" #include "CustomLevels.h" #include "Entity.h" #include "Font.h" #include "Graphics.h" #include "KeyPoll.h" #include "Localization.h" #include "Map.h" #include "Script.h" #include "UtilityClass.h" #include "VFormat.h" namespace level_debugger { bool active = false; bool should_pause = true; bool tab_held = false; bool debug_held = false; bool forced = false; // Moving entities/blocks bool mouse_held = false; int held_entity = -1; int held_block = -1; int grabber_offset_x = 0; int grabber_offset_y = 0; bool right_mouse_held = false; bool is_pausing(void) { return active && should_pause; } bool is_active(void) { return active; } void toggle_active(void) { active = !active; } bool mouse_within(SDL_Rect* rect) { SDL_Point mouse = { key.mousex, key.mousey }; return SDL_PointInRect(&mouse, rect); } void set_forced(void) { forced = true; } void input(void) { if (!forced && (!map.custommode || map.custommodeforreal)) { active = false; return; } if (key.isDown(SDLK_y)) { if (!debug_held) { debug_held = true; active = !active; } } else { debug_held = false; } if (!active) { return; } if (key.isDown(SDLK_TAB)) { if (!tab_held) { tab_held = true; should_pause = !should_pause; } } else { tab_held = false; } for (int i = 0; i < (int) obj.entities.size(); i++) { SDL_Rect bounding_box = { obj.entities[i].xp + obj.entities[i].cx, obj.entities[i].yp + obj.entities[i].cy - map.ypos, obj.entities[i].w, obj.entities[i].h }; if (key.leftbutton) { if (mouse_within(&bounding_box)) { if (!mouse_held) { mouse_held = true; held_entity = i; grabber_offset_x = key.mousex - obj.entities[i].xp; grabber_offset_y = key.mousey - obj.entities[i].yp; if (!key.keymap[SDLK_LSHIFT] && !key.keymap[SDLK_RSHIFT]) { for (int j = 0; j < (int) obj.blocks.size(); j++) { if (obj.entities[i].xp == obj.blocks[j].rect.x && obj.entities[i].yp == obj.blocks[j].rect.y) { held_block = j; } } } } break; } } else { mouse_held = false; held_entity = -1; held_block = -1; } if (key.rightbutton) { if (mouse_within(&bounding_box)) { if (!right_mouse_held) { right_mouse_held = true; obj.entities[i].state = obj.entities[i].onentity; obj.entities[i].statedelay = -1; } break; } } else { right_mouse_held = false; } } if (held_entity == -1) { for (int i = 0; i < (int) obj.blocks.size(); i++) { SDL_Rect bounding_box = { obj.blocks[i].rect.x, obj.blocks[i].rect.y - map.ypos, obj.blocks[i].rect.w, obj.blocks[i].rect.h }; if (key.leftbutton) { if (mouse_within(&bounding_box)) { if (!mouse_held) { mouse_held = true; held_block = i; grabber_offset_x = key.mousex - obj.blocks[i].rect.x; grabber_offset_y = key.mousey - obj.blocks[i].rect.y; } break; } } else { held_entity = -1; mouse_held = false; held_block = -1; } } } } void logic(void) { if (!active) { return; } if (INBOUNDS_VEC(held_entity, obj.entities)) { int new_xp = key.mousex - grabber_offset_x; int new_yp = key.mousey - grabber_offset_y; if (key.isDown(SDLK_LSHIFT) || key.isDown(SDLK_RSHIFT)) { new_xp -= new_xp % 8; new_yp -= new_yp % 8; } obj.entities[held_entity].xp = new_xp; obj.entities[held_entity].yp = new_yp; obj.entities[held_entity].lerpoldxp = new_xp; obj.entities[held_entity].lerpoldyp = new_yp; obj.entities[held_entity].oldxp = new_xp; obj.entities[held_entity].oldyp = new_yp; } if (INBOUNDS_VEC(held_block, obj.blocks)) { int new_xp = key.mousex - grabber_offset_x; int new_yp = key.mousey - grabber_offset_y; if (key.isDown(SDLK_LSHIFT) || key.isDown(SDLK_RSHIFT)) { new_xp -= new_xp % 8; new_yp -= new_yp % 8; } obj.blocks[held_block].xp = new_xp; obj.blocks[held_block].yp = new_yp; obj.blocks[held_block].rect.x = new_xp; obj.blocks[held_block].rect.y = new_yp; } } void render_info(int y, const char* text) { font::print(PR_BOR | PR_FONT_8X8, 5, 32 + (10 * y), text, 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2)); } void render_coords(int y, const char* text, int first, int second) { char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf(buffer, sizeof(buffer), "{text}: ({first},{second})", "text:str, first:int, second:int", text, first, second); render_info(y, buffer); } void render_info(int y, const char* text, std::string value) { char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf(buffer, sizeof(buffer), "{text}: {value}", "text:str, value:str", text, value.c_str()); render_info(y, buffer); } void render(void) { if (!active) { return; } int hovered = -1; bool hovered_entity = true; SDL_Rect hover_box; for (int i = 0; i < (int) obj.entities.size(); i++) { SDL_Rect bounding_box = { obj.entities[i].xp + obj.entities[i].cx, obj.entities[i].yp + obj.entities[i].cy - map.ypos, obj.entities[i].w, obj.entities[i].h }; if (hovered == -1 && mouse_within(&bounding_box)) { hovered = i; hovered_entity = true; hover_box = bounding_box; } graphics.draw_rect(bounding_box.x, bounding_box.y, bounding_box.w, bounding_box.h, graphics.getRGB(15, 90, 90)); // For gravity lines, show the true hitbox. if (obj.entities[i].type == 9) { graphics.draw_rect(bounding_box.x - 1, bounding_box.y + 1, bounding_box.w + 2, bounding_box.h, graphics.getRGB(90, 90, 15)); } else if (obj.entities[i].type == 10) { graphics.fill_rect(bounding_box.x - 2, bounding_box.y - 1, bounding_box.w + 1, bounding_box.h + 2, graphics.getRGB(90, 90, 15)); } } for (int i = 0; i < (int) obj.blocks.size(); i++) { SDL_Rect bounding_box = { obj.blocks[i].rect.x, obj.blocks[i].rect.y - map.ypos, obj.blocks[i].rect.w, obj.blocks[i].rect.h }; if (hovered == -1 && mouse_within(&bounding_box)) { hovered = i; hovered_entity = false; hover_box = bounding_box; } graphics.draw_rect(bounding_box.x, bounding_box.y, bounding_box.w, bounding_box.h, graphics.getRGB(90, 15, 15)); } int line = 0; if (key.isDown(SDLK_u)) { SDL_Color on = graphics.getRGB(220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2)); SDL_Color off = graphics.getRGB(220 / 1.5 - (help.glow), 220 / 1.5 - (help.glow), 255 / 1.5 - (help.glow / 2)); graphics.set_blendmode(SDL_BLENDMODE_BLEND); graphics.fill_rect(NULL, 0, 0, 0, 127); graphics.set_blendmode(SDL_BLENDMODE_NONE); int x = 0; int y = 0; for (int i = 0; i < (int) SDL_arraysize(obj.flags); i++) { SDL_Color color = obj.flags[i] ? on : off; font::print(PR_BOR | PR_FONT_8X8, 48 + x * 24, 48 + y * 16, help.String(i), color.r, color.g, color.b); x++; if (x >= 10) { x = 0; y++; } } } else if (hovered == -1) { render_coords(line++, "Room", game.roomx % 100, game.roomy % 100); render_coords(line++, "Cursor", key.mousex, key.mousey); render_info(line++, "Entities", help.String(obj.entities.size())); line++; render_info(line++, "State", help.String(game.state)); render_info(line++, "State Delay", help.String(game.statedelay)); line++; render_info(line++, "AEM Target", help.String(script.i)); render_info(line++, "Warp Background", help.String(cl.getroomprop(game.roomx % 100, game.roomy % 100)->warpdir)); line++; if (script.running) { render_info(line++, "Script", script.scriptname); render_info(line++, "Delay", help.String(script.scriptdelay)); render_info(line++, "Position", help.String(script.position)); line++; render_info(line++, "Loop Line", help.String(script.looppoint)); render_info(line++, "Loop Count", help.String(script.loopcount)); } } else { if (hovered_entity) { entclass* entity = &obj.entities[hovered]; render_info(line++, "Index", help.String(hovered)); render_coords(line++, "Position", entity->xp, entity->yp); render_coords(line++, "Size", entity->w, entity->h); line++; render_info(line++, "Rule", help.String(entity->rule)); render_info(line++, "Type", help.String(entity->type)); render_info(line++, "Behave", help.String(entity->behave)); render_info(line++, "Para", help.String(entity->para)); render_info(line++, "State", help.String(entity->state)); line++; render_info(line++, "Tile", help.String(entity->tile)); render_info(line++, "Draw Frame", help.String(entity->drawframe)); render_info(line++, "Size", help.String(entity->size)); render_info(line++, "Direction", help.String(entity->dir)); line++; // Mostly contains duplicates, but for ease of use switch (entity->type) { case 0: // Player render_info(line++, "Gravity", help.String(game.gravitycontrol)); render_info(line++, "Checkpoint", help.String(game.savepoint)); break; case 1: // Moving platforms and enemies render_info(line++, "Speed", help.String(entity->para)); render_info(line++, "Movement type", help.String(entity->behave)); break; case 7: // Trinkets render_info(line++, "ID", help.String(entity->para)); break; case 8: // Checkpoints render_info(line++, "ID", help.String(entity->para)); render_info(line++, "Active", game.savepoint == entity->para ? "True" : "False"); break; case 9: // Horizontal gravity lines render_info(line++, "Horizontal"); break; case 10: // Vertical gravity lines render_info(line++, "Vertical"); break; } graphics.draw_rect(hover_box.x, hover_box.y, hover_box.w, hover_box.h, graphics.getRGB(32, 255 - help.glow, 255 - help.glow)); } else { blockclass* block = &obj.blocks[hovered]; render_info(line++, "Index", help.String(hovered)); render_coords(line++, "Position", block->rect.x, block->rect.y); render_coords(line++, "Size", block->rect.w, block->rect.h); line++; if (block->type == TRIGGER || block->type == ACTIVITY) { render_info(line++, "Script", block->script); render_info(line++, "State", help.String(block->trigger)); } else if (block->type == DIRECTIONAL) { render_info(line++, "Direction", help.String(block->trigger)); } graphics.draw_rect(hover_box.x, hover_box.y, hover_box.w, hover_box.h, graphics.getRGB(255 - help.glow, 32, 32)); } } const char* text; if (should_pause) { text = loc::gettext("[Press {button} to unfreeze gameplay]"); } else { text = loc::gettext("[Press {button} to freeze gameplay]"); } char buffer[SCREEN_WIDTH_CHARS + 1]; vformat_buf( buffer, sizeof(buffer), text, "button:str", "TAB" ); font::print(PR_BOR, 5, 14, buffer, 220 - (help.glow), 220 - (help.glow), 255 - (help.glow / 2)); } }