#define ED_DEFINITION
#include "Editor.h"

#include <string>
#include <vector>

#include "Constants.h"
#include "CustomLevels.h"
#include "DeferCallbacks.h"
#include "Entity.h"
#include "Enums.h"
#include "Font.h"
#include "Game.h"
#include "Graphics.h"
#include "GraphicsUtil.h"
#include "KeyPoll.h"
#include "Localization.h"
#include "Map.h"
#include "Maths.h"
#include "Music.h"
#include "Screen.h"
#include "Script.h"
#include "UTF8.h"
#include "UtilityClass.h"
#include "VFormat.h"
#include "Vlogging.h"

#define SCRIPT_LINE_PADDING 6

editorclass::editorclass(void)
{
    reset();

    register_tool(EditorTool_WALLS, "Walls", "1", SDLK_1, false);
    register_tool(EditorTool_BACKING, "Backing", "2", SDLK_2, false);
    register_tool(EditorTool_SPIKES, "Spikes", "3", SDLK_3, false);
    register_tool(EditorTool_TRINKETS, "Trinkets", "4", SDLK_4, false);
    register_tool(EditorTool_CHECKPOINTS, "Checkpoints", "5", SDLK_5, false);
    register_tool(EditorTool_DISAPPEARING_PLATFORMS, "Disappearing Platforms", "6", SDLK_6, false);
    register_tool(EditorTool_CONVEYORS, "Conveyors", "7", SDLK_7, false);
    register_tool(EditorTool_MOVING_PLATFORMS, "Moving Platforms", "8", SDLK_8, false);
    register_tool(EditorTool_ENEMIES, "Enemies", "9", SDLK_9, false);
    register_tool(EditorTool_GRAVITY_LINES, "Gravity Lines", "0", SDLK_0, false);
    register_tool(EditorTool_ROOMTEXT, "Roomtext", "R", SDLK_r, false);
    register_tool(EditorTool_TERMINALS, "Terminals", "T", SDLK_t, false);
    register_tool(EditorTool_SCRIPTS, "Script Boxes", "Y", SDLK_y, false);
    register_tool(EditorTool_WARP_TOKENS, "Warp Tokens", "U", SDLK_u, false);
    register_tool(EditorTool_WARP_LINES, "Warp Lines", "I", SDLK_i, false);
    register_tool(EditorTool_CREWMATES, "Crewmates", "O", SDLK_o, false);
    register_tool(EditorTool_START_POINT, "Start Point", "P", SDLK_p, false);

    static const short basic[] = {
        121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121, 121,
        160, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 80, 80, 80,
        120, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121,
        121, 160, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 80, 80,
        80, 120, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121,
        121, 160, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 80, 80,
        80, 120, 121, 121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121,
        121, 160, 82, 82, 82, 82, 82, 82, 82, 0, 82, 82, 82, 82, 81, 81, 81, 42, 121,
        121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121, 121, 160, 121,
        121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 80, 80, 80, 120, 121,
        121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 121, 121, 121, 160, 121,
        121, 121, 121, 121, 121, 121, 160, 121, 121, 121, 121, 80, 80, 80, 120, 121,
        162, 121, 162, 121, 162, 121, 161, 121, 162, 121, 162, 121, 162, 121, 161, 121,
        162, 121, 162, 121, 162, 121, 161, 121, 162, 121, 162, 80, 0, 80, 2, 121, 162,
        121, 162, 121, 162, 121, 161, 121, 162, 121, 162, 121, 162, 121, 161, 82, 122,
        82, 122, 82, 122, 82, 1, 82, 122, 82, 122, 81, 41, 81, 0
    };

    std::vector<int> basic_vec;
    basic_vec.assign(basic, basic + SDL_arraysize(basic));
    autotile_types["basic"] = basic_vec;

    static const short lab_cyan[] = {
        121, 184, 121, 184, 24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104,
        144, 104, 144, 80, 120, 80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 121, 184,
        121, 184, 24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104, 144, 104,
        144, 80, 120, 80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 26, 162, 26, 162, 25,
        161, 25, 106, 26, 162, 26, 162, 25, 161, 25, 106, 82, 122, 82, 186, 81, 0, 81, 0,
        82, 122, 82, 122, 66, 0, 66, 0, 26, 162, 26, 162, 25, 161, 25, 106, 26, 162, 26,
        162, 25, 161, 25, 106, 82, 186, 82, 186, 65, 0, 65, 0, 82, 186, 82, 186, 81, 199,
        81, 42, 121, 184, 121, 184, 24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160,
        104, 144, 104, 144, 80, 120, 80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 121, 184,
        121, 184, 24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104, 144, 104, 144,
        80, 120, 80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 26, 162, 26, 162, 25, 105, 25,
        161, 26, 162, 26, 162, 25, 105, 25, 161, 82, 146, 82, 146, 81, 0, 81, 64, 82, 146, 82,
        146, 66, 0, 66, 2, 26, 162, 26, 162, 25, 105, 25, 161, 26, 162, 26, 162, 25, 105, 25,
        161, 82, 122, 82, 122, 65, 0, 65, 1, 82, 122, 82, 122, 81, 41, 81, 0
    };

    std::vector<int> lab_cyan_vec;
    lab_cyan_vec.assign(lab_cyan, lab_cyan + SDL_arraysize(lab_cyan));
    autotile_types["lab_cyan"] = lab_cyan_vec;

    static const short lab_red[] = {
        121, 190, 121, 190, 30, 160, 30, 160, 121, 190, 121, 190, 30, 160, 30, 160, 110, 150,
        110, 150, 80, 120, 80, 151, 110, 150, 110, 150, 80, 191, 80, 120, 121, 190, 121, 190,
        30, 160, 30, 160, 121, 190, 121, 190, 30, 160, 30, 160, 110, 150, 110, 150, 80, 120, 80,
        151, 110, 150, 110, 150, 80, 191, 80, 120, 32, 162, 32, 162, 31, 161, 31, 112, 32, 162,
        32, 162, 31, 161, 31, 112, 82, 122, 82, 122, 81, 0, 81, 0, 82, 122, 82, 122, 72, 0, 81,
        0, 32, 162, 32, 162, 31, 161, 31, 112, 32, 162, 32, 162, 31, 161, 31, 112, 82, 192, 82,
        192, 71, 0, 71, 0, 82, 192, 82, 192, 81, 70, 81, 42, 121, 190, 121, 190, 30, 160, 30,
        160, 121, 190, 121, 190, 30, 160, 30, 160, 110, 150, 110, 150, 80, 120, 80, 151, 110,
        150, 110, 150, 80, 191, 80, 120, 121, 190, 121, 190, 30, 160, 30, 160, 121, 190, 121,
        190, 30, 160, 30, 160, 110, 150, 110, 150, 80, 120, 121, 151, 110, 150, 110, 150, 80,
        191, 80, 120, 32, 162, 32, 162, 31, 111, 31, 161, 32, 162, 32, 162, 31, 111, 31, 161,
        82, 152, 82, 152, 81, 0, 81, 76, 82, 152, 82, 152, 72, 0, 72, 2, 32, 162, 32, 162, 31,
        111, 31, 161, 32, 162, 32, 162, 31, 111, 31, 161, 82, 122, 82, 122, 71, 156, 71, 1, 82,
        122, 82, 122, 81, 41, 81, 0
    };

    std::vector<int> lab_red_vec;
    lab_red_vec.assign(lab_red, lab_red + SDL_arraysize(lab_red));
    autotile_types["lab_red"] = lab_red_vec;

    static const short lab_pink[] = {
        121, 184, 121, 184, 24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104, 144,
        104, 144, 80, 120, 80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 121, 184, 121, 184,
        24, 160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104, 144, 104, 144, 80, 120,
        80, 145, 104, 144, 104, 144, 80, 185, 80, 120, 26, 162, 26, 162, 25, 161, 25, 106, 26,
        162, 26, 162, 25, 161, 25, 106, 82, 122, 82, 122, 81, 0, 81, 0, 82, 122, 82, 122, 66,
        0, 66, 0, 26, 162, 26, 162, 25, 161, 25, 106, 26, 162, 26, 162, 25, 161, 25, 106, 82,
        186, 82, 186, 65, 0, 65, 0, 82, 122, 82, 122, 81, 64, 81, 42, 121, 184, 121, 184, 24,
        160, 24, 160, 121, 184, 121, 184, 24, 160, 24, 160, 104, 144, 104, 144, 80, 120, 80,
        145, 104, 144, 104, 144, 80, 185, 80, 120, 121, 184, 121, 184, 24, 160, 24, 160, 121,
        184, 121, 184, 24, 160, 24, 160, 104, 144, 104, 144, 80, 120, 80, 145, 104, 144, 104,
        144, 80, 185, 80, 120, 26, 162, 26, 162, 25, 105, 25, 161, 26, 162, 26, 162, 25, 105,
        25, 161, 82, 146, 82, 146, 81, 0, 81, 113, 82, 146, 82, 146, 66, 0, 66, 2, 26, 162,
        26, 162, 25, 105, 25, 161, 26, 162, 26, 162, 25, 105, 25, 161, 82, 122, 82, 122, 65,
        0, 65, 1, 82, 122, 82, 122, 81, 41, 81, 0
    };

    std::vector<int> lab_pink_vec;
    lab_pink_vec.assign(lab_pink, lab_pink + SDL_arraysize(lab_pink));
    autotile_types["lab_pink"] = lab_pink_vec;

    static const short lab_yellow[] = {
        121, 175, 121, 175, 15, 160, 15, 160, 121, 175, 121, 175, 15, 160, 15, 160, 95,
        135, 95, 135, 80, 120, 80, 136, 95, 135, 95, 135, 80, 176, 80, 120, 121, 175,
        121, 175, 15, 160, 15, 160, 121, 175, 121, 175, 15, 160, 15, 160, 95, 135, 95,
        135, 80, 120, 80, 136, 95, 135, 95, 135, 80, 176, 80, 120, 17, 162, 17, 162, 16,
        161, 16, 97, 17, 162, 17, 162, 16, 161, 16, 97, 82, 122, 82, 122, 81, 0, 81, 0,
        82, 122, 82, 122, 57, 0, 57, 0, 17, 162, 17, 162, 16, 161, 16, 97, 17, 162, 17,
        162, 16, 161, 16, 97, 82, 177, 82, 177, 56, 0, 56, 0, 82, 177, 82, 177, 81, 55,
        81, 42, 121, 175, 121, 175, 15, 160, 15, 160, 121, 175, 121, 175, 15, 160, 15,
        160, 95, 135, 95, 135, 80, 120, 80, 136, 95, 135, 95, 135, 80, 176, 80, 120, 121,
        175, 121, 175, 15, 160, 15, 160, 121, 175, 121, 175, 15, 160, 15, 160, 95, 135,
        95, 135, 80, 120, 80, 136, 95, 135, 95, 135, 80, 176, 80, 120, 17, 162, 17, 162,
        16, 96, 16, 161, 17, 162, 17, 162, 16, 96, 16, 161, 82, 137, 82, 137, 81, 0, 81,
        27, 82, 137, 82, 137, 57, 0, 57, 2, 17, 162, 17, 162, 16, 162, 16, 161, 17, 162,
        17, 162, 16, 96, 16, 161, 82, 122, 82, 122, 56, 0, 82, 1, 82, 122, 82, 122, 81,
        41, 81, 0
    };

    std::vector<int> lab_yellow_vec;
    lab_yellow_vec.assign(lab_yellow, lab_yellow + SDL_arraysize(lab_yellow));
    autotile_types["lab_yellow"] = lab_yellow_vec;

    static const short lab_green[] = {
        121, 181, 121, 181, 21, 160, 21, 160, 121, 181, 121, 181, 21, 160, 21, 160, 101,
        141, 101, 141, 80, 120, 80, 142, 101, 141, 101, 141, 80, 182, 80, 120, 121, 181,
        121, 181, 21, 160, 21, 160, 121, 181, 121, 181, 21, 160, 21, 160, 101, 141, 101,
        141, 80, 120, 80, 142, 101, 141, 101, 141, 80, 182, 80, 120, 23, 162, 23, 162, 22,
        161, 22, 103, 23, 162, 23, 162, 22, 161, 22, 103, 82, 122, 82, 122, 81, 0, 81, 0,
        82, 122, 82, 122, 63, 0, 63, 0, 23, 162, 23, 162, 22, 161, 22, 103, 23, 162, 23,
        162, 22, 161, 22, 103, 82, 183, 82, 183, 62, 0, 62, 0, 82, 183, 82, 183, 81, 61,
        81, 42, 121, 181, 121, 181, 21, 160, 21, 160, 121, 181, 121, 181, 21, 160, 21,
        160, 101, 141, 101, 141, 80, 120, 80, 142, 101, 141, 101, 141, 80, 182, 80, 120,
        121, 181, 121, 181, 21, 160, 21, 160, 121, 181, 121, 181, 21, 160, 21, 160, 101,
        141, 101, 141, 80, 120, 80, 142, 101, 141, 101, 141, 80, 182, 80, 120, 23, 162,
        23, 162, 22, 102, 22, 161, 23, 162, 23, 162, 22, 102, 22, 161, 82, 143, 82, 143,
        81, 0, 81, 0, 82, 143, 82, 143, 63, 0, 63, 2, 23, 162, 23, 162, 22, 102, 22, 161,
        23, 162, 23, 162, 22, 102, 22, 161, 82, 122, 82, 122, 62, 0, 62, 1, 82, 122, 82,
        122, 81, 41, 81, 0
    };

    std::vector<int> lab_green_vec;
    lab_green_vec.assign(lab_green, lab_green + SDL_arraysize(lab_green));
    autotile_types["lab_green"] = lab_green_vec;

    static const short outside[] = {
        2, 0, 2, 0, 1, 2, 1, 2, 2, 0, 2, 2, 1, 2, 1, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0,
        0, 2, 2, 2, 2, 2, 0, 2, 0, 1, 2, 1, 2, 2, 0, 2, 0, 1, 2, 1, 2, 0, 0, 2, 0, 2, 2,
        2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
        1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 1, 2, 1,
        2, 2, 0, 2, 0, 1, 2, 1, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 0,
        2, 0, 1, 2, 1, 2, 2, 0, 2, 0, 1, 2, 1, 2, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 2,
        2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
    };

    std::vector<int> outside_vec;
    outside_vec.assign(outside, outside + SDL_arraysize(outside));
    autotile_types["outside"] = outside_vec;

    // Everything gets initialized to 0 by default
    static const short none[256] = {};

    std::vector<int> none_vec;
    none_vec.assign(none, none + SDL_arraysize(none));
    autotile_types["none"] = none_vec;

    SDL_zeroa(tileset_min_colour);
    SDL_zeroa(tileset_max_colour);
    SDL_zeroa(tileset_min_colour_direct);
    SDL_zeroa(tileset_max_colour_direct);

    register_tileset(EditorTileset_SPACE_STATION, "Space Station");
    register_tileset(EditorTileset_OUTSIDE, "Outside");
    register_tileset(EditorTileset_LAB, "Lab");
    register_tileset(EditorTileset_WARP_ZONE, "Warp Zone");
    register_tileset(EditorTileset_SHIP, "Ship");

    register_tilecol(EditorTileset_SPACE_STATION, -1, "basic", 80, "basic", 680);
    register_tilecol(EditorTileset_SPACE_STATION, 0, "basic", 83, "basic", 680);
    register_tilecol(EditorTileset_SPACE_STATION, 1, "basic", 86, "basic", 698);
    register_tilecol(EditorTileset_SPACE_STATION, 2, "basic", 89, "basic", 695);
    register_tilecol(EditorTileset_SPACE_STATION, 3, "basic", 92, "basic", 683);
    register_tilecol(EditorTileset_SPACE_STATION, 4, "basic", 95, "basic", 689);
    register_tilecol(EditorTileset_SPACE_STATION, 5, "basic", 98, "basic", 680);
    register_tilecol(EditorTileset_SPACE_STATION, 6, "basic", 101, "basic", 695);
    register_tilecol(EditorTileset_SPACE_STATION, 7, "basic", 104, "basic", 704);
    register_tilecol(EditorTileset_SPACE_STATION, 8, "basic", 107, "basic", 689);
    register_tilecol(EditorTileset_SPACE_STATION, 9, "basic", 110, "basic", 686);
    register_tilecol(EditorTileset_SPACE_STATION, 10, "basic", 113, "basic", 698);

    register_tilecol(EditorTileset_SPACE_STATION, 11, "basic", 283, "basic", 695);
    register_tilecol(EditorTileset_SPACE_STATION, 12, "basic", 286, "basic", 686);
    register_tilecol(EditorTileset_SPACE_STATION, 13, "basic", 289, "basic", 704);
    register_tilecol(EditorTileset_SPACE_STATION, 14, "basic", 292, "basic", 701);
    register_tilecol(EditorTileset_SPACE_STATION, 15, "basic", 295, "basic", 698);
    register_tilecol(EditorTileset_SPACE_STATION, 16, "basic", 298, "basic", 683);
    register_tilecol(EditorTileset_SPACE_STATION, 17, "basic", 301, "basic", 704);
    register_tilecol(EditorTileset_SPACE_STATION, 18, "basic", 304, "basic", 701);
    register_tilecol(EditorTileset_SPACE_STATION, 19, "basic", 307, "basic", 698);
    register_tilecol(EditorTileset_SPACE_STATION, 20, "basic", 310, "basic", 692);
    register_tilecol(EditorTileset_SPACE_STATION, 21, "basic", 313, "basic", 686);

    register_tilecol(EditorTileset_SPACE_STATION, 22, "basic", 483, "basic", 695);
    register_tilecol(EditorTileset_SPACE_STATION, 23, "basic", 486, "basic", 683);
    register_tilecol(EditorTileset_SPACE_STATION, 24, "basic", 489, "basic", 689);
    register_tilecol(EditorTileset_SPACE_STATION, 25, "basic", 492, "basic", 704);
    register_tilecol(EditorTileset_SPACE_STATION, 26, "basic", 495, "basic", 680);
    register_tilecol(EditorTileset_SPACE_STATION, 27, "basic", 498, "basic", 695);
    register_tilecol(EditorTileset_SPACE_STATION, 28, "basic", 501, "basic", 689);
    register_tilecol(EditorTileset_SPACE_STATION, 29, "basic", 504, "basic", 692);
    register_tilecol(EditorTileset_SPACE_STATION, 30, "basic", 507, "basic", 689);
    register_tilecol(EditorTileset_SPACE_STATION, 31, "basic", 510, "basic", 698);

    register_tilecol(EditorTileset_OUTSIDE, 0, "basic", 480, "outside", 680, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 1, "basic", 483, "outside", 683, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 2, "basic", 486, "outside", 686, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 3, "basic", 489, "outside", 689, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 4, "basic", 492, "outside", 692, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 5, "basic", 495, "outside", 695, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 6, "basic", 498, "outside", 698, false, true);
    register_tilecol(EditorTileset_OUTSIDE, 7, "basic", 501, "outside", 701, false, true);

    register_tilecol(EditorTileset_LAB, 0, "lab_cyan", 280, "none", 713);
    register_tilecol(EditorTileset_LAB, 1, "lab_red", 283, "none", 713);
    register_tilecol(EditorTileset_LAB, 2, "lab_pink", 286, "none", 713);
    register_tilecol(EditorTileset_LAB, 3, "basic", 289, "none", 713);
    register_tilecol(EditorTileset_LAB, 4, "lab_yellow", 292, "none", 713);
    register_tilecol(EditorTileset_LAB, 5, "lab_green", 295, "none", 713);
    register_tilecol(EditorTileset_LAB, 6, "none", 0, "none", 713, true);

    register_tilecol(EditorTileset_WARP_ZONE, 0, "basic", 80, "none", 120);
    register_tilecol(EditorTileset_WARP_ZONE, 1, "basic", 83, "none", 123);
    register_tilecol(EditorTileset_WARP_ZONE, 2, "basic", 86, "none", 126);
    register_tilecol(EditorTileset_WARP_ZONE, 3, "basic", 89, "none", 129);
    register_tilecol(EditorTileset_WARP_ZONE, 4, "basic", 92, "none", 132);
    register_tilecol(EditorTileset_WARP_ZONE, 5, "basic", 95, "none", 135);
    register_tilecol(EditorTileset_WARP_ZONE, 6, "basic", 98, "none", 138);

    register_tilecol(EditorTileset_SHIP, 0, "basic", 101, "basic", 741);
    register_tilecol(EditorTileset_SHIP, 1, "basic", 104, "basic", 744);
    register_tilecol(EditorTileset_SHIP, 2, "basic", 107, "basic", 747);
    register_tilecol(EditorTileset_SHIP, 3, "basic", 110, "basic", 750);
    register_tilecol(EditorTileset_SHIP, 4, "basic", 113, "basic", 753);
    register_tilecol(EditorTileset_SHIP, 5, "basic", 116, "basic", 756);
}

void editorclass::register_tileset(EditorTilesets tileset, const char* name)
{
    tileset_names[tileset] = name;
}

void editorclass::register_tilecol(
    EditorTilesets tileset,
    const int index,
    const char* foreground_type,
    const int foreground_base,
    const char* background_type,
    const int background_base,
    const bool direct,
    const bool bg_ignores_walls
) {
    EditorTilecolInfo info;
    info.foreground_type = foreground_type;
    info.foreground_base = foreground_base;
    info.background_type = background_type;
    info.background_base = background_base;
    info.direct_mode = direct;
    info.bg_ignores_walls = bg_ignores_walls;
    tileset_colors[tileset][index] = info;

    if (!direct)
    {
        tileset_min_colour[tileset] = SDL_min(tileset_min_colour[tileset], index);
        tileset_max_colour[tileset] = SDL_max(tileset_max_colour[tileset], index);
    }

    tileset_min_colour_direct[tileset] = SDL_min(tileset_min_colour_direct[tileset], index);
    tileset_max_colour_direct[tileset] = SDL_max(tileset_max_colour_direct[tileset], index);
}

void editorclass::register_tilecol(
    EditorTilesets tileset,
    const int index,
    const char* foreground_type,
    const int foreground_base,
    const char* background_type,
    const int background_base,
    const bool bg_ignores_walls
) {
    register_tilecol(tileset, index, foreground_type, foreground_base, background_type, background_base, bg_ignores_walls, false);
}

void editorclass::register_tilecol(
    EditorTilesets tileset,
    const int index,
    const char* foreground_type,
    const int foreground_base,
    const char* background_type,
    const int background_base
) {
    register_tilecol(tileset, index, foreground_type, foreground_base, background_type, background_base, false);
}

void editorclass::reset(void)
{
    current_tool = EditorTool_WALLS;

    roomnamehide = 0;
    z_modifier = false;
    x_modifier = false;
    c_modifier = false;
    v_modifier = false;
    h_modifier = false;
    b_modifier = false;
    f_modifier = false;
    toolbox_open = false;
    help_open = false;
    shiftkey = false;
    saveandquit = false;
    note = "";
    note_timer = 0;
    old_note_timer = 0;
    backspace_held = false;
    current_text_mode = TEXT_NONE;

    warp_token_entity = -1;

    text_entity = 0;
    scripttexttype = 0;

    direct_mode_tile = 0;
    direct_mode_drawer = 0;
    entcol = 0;

    old_tilex = 0;
    old_tiley = 0;
    tilex = 0;
    tiley = 0;
    levx = 0;
    levy = 0;
    keydelay = 0;
    lclickdelay = 0;
    savekey = false;
    loadkey = false;
    updatetiles = true;
    changeroom = true;

    entframe = 0;
    entframedelay = 0;

    SDL_zeroa(kludgewarpdir);

    script_buffer.clear();

    clear_script_buffer();

    script_cursor_x = 0;
    script_cursor_y = 0;
    script_offset = 0;
    lines_visible = 25;
    current_script = "null";

    script_list_offset = 0;
    selected_script = 0;

    return_message_timer = 0;
    old_return_message_timer = 0;

    ghosts.clear();
    current_ghosts = 0;

    loaded_filepath = "";

    state = EditorState_DRAW;
    substate = EditorSubState_MAIN;
}

void editorclass::show_note(const char* text)
{
    note_timer = 45;
    note = text;
}

void editorclass::register_tool(EditorTools tool, const char* name, const char* keychar, const SDL_KeyCode key, const bool shift)
{
    tool_names[tool] = name;
    tool_key_chars[tool] = keychar;
    tool_keys[tool] = key;
    tool_requires_shift[tool] = shift;
}

void editorclass::load_script_in_editor(const std::string& name)
{
    // Load script t into the script editor
    clear_script_buffer();

    for (size_t i = 0; i < script.customscripts.size(); i++)
    {
        if (script.customscripts[i].name == name)
        {
            script_buffer = script.customscripts[i].contents;
            break;
        }
    }

    if (script_buffer.empty())
    {
        // Always have one line or we'll have problems
        script_buffer.resize(1);
    }
}

void editorclass::remove_script(const std::string& name)
{
    for (size_t i = 0; i < script.customscripts.size(); i++)
    {
        if (script.customscripts[i].name == name)
        {
            script.customscripts.erase(script.customscripts.begin() + i);
            break;
        }
    }
}

void editorclass::create_script(const std::string& name, const std::vector<std::string>& contents)
{
    // Add a script. If there's an old one, delete it.

    remove_script(name);

    Script script_;
    script_.name = name;
    script_.contents = contents;

    script.customscripts.push_back(script_);
}

void editorclass::create_script(const std::string& name)
{
    // Add an empty script.

    Script script_;
    script_.name = name;
    script_.contents.resize(1);

    script.customscripts.push_back(script_);
}

bool editorclass::script_exists(const std::string& name)
{
    for (size_t i = 0; i < script.customscripts.size(); i++)
    {
        if (script.customscripts[i].name == name)
        {
            return true;
        }
    }
    return false;
}


void editorclass::clear_script_buffer(void)
{
    script_buffer.clear();
}

void editorclass::remove_line(int t)
{
    //Remove line t from the script
    if ((int) script_buffer.size() > 1)
    {
        script_buffer.erase(script_buffer.begin() + t);
    }
}

void editorclass::insert_line(int t)
{
    //insert a blank line into script at line t
    script_buffer.insert(script_buffer.begin() + t, "");
}

static void editormenurender(int tr, int tg, int tb)
{
    extern editorclass ed;

    switch (game.currentmenuname)
    {
    case Menu::ed_settings:
        font::print(PR_2X | PR_CEN, -1, 75, loc::gettext("Map Settings"), tr, tg, tb);
        if (game.currentmenuoption == 3)
        {
            if (!game.ghostsenabled)
                font::print(0, 2, 230, loc::gettext("Editor ghost trail is OFF"), tr/2, tg/2, tb/2);
            else
                font::print(0, 2, 230, loc::gettext("Editor ghost trail is ON"), tr, tg, tb);
        }
        break;
    case Menu::ed_desc:
    {
        const std::string input_text = key.keybuffer + ((ed.entframe < 2) ? "_" : " ");

        if (ed.current_text_mode == TEXT_TITLE)
        {
            font::print(PR_2X | PR_CEN | PR_FONT_LEVEL, -1, 35, input_text, tr, tg, tb);
        }
        else
        {
            const char* title = cl.title.c_str();
            const bool title_is_gettext = translate_title(cl.title);
            if (title_is_gettext)
            {
                title = loc::gettext(title);
            }
            font::print(PR_2X | PR_CEN | (title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), -1, 35, title, tr, tg, tb);
        }

        bool creator_is_gettext;
        const char* creator;
        if (ed.current_text_mode == TEXT_CREATOR)
        {
            creator_is_gettext = false;
            creator = input_text.c_str();
        }
        else
        {
            creator_is_gettext = translate_creator(cl.creator);
            creator = cl.creator.c_str();
        }
        if (creator_is_gettext)
        {
            creator = loc::gettext(creator);
        }

        int sp = SDL_max(10, font::height(PR_FONT_LEVEL));
        graphics.print_level_creator((creator_is_gettext ? PR_FONT_INTERFACE : PR_FONT_LEVEL), 60, creator, tr, tg, tb);

        font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp, (ed.current_text_mode == TEXT_WEBSITE) ? input_text : cl.website, tr, tg, tb);
        font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 3, (ed.current_text_mode == TEXT_DESC1) ? input_text : cl.Desc1, tr, tg, tb);
        font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 4, (ed.current_text_mode == TEXT_DESC2) ? input_text : cl.Desc2, tr, tg, tb);


        if (ed.current_text_mode == TEXT_DESC3)
        {
            font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 5, input_text, tr, tg, tb);
        }
        else if (sp <= 10)
        {
            font::print(PR_CEN | PR_FONT_LEVEL, -1, 60 + sp * 5, cl.Desc3, tr, tg, tb);
        }

        const char* label = loc::gettext("Font: ");
        int len_label = font::len(0, label);
        const char* name = font::get_level_font_display_name();

        int font_x = 2 + len_label;
        uint32_t font_flags = PR_FONT_LEVEL;
        if (font::is_rtl(PR_FONT_INTERFACE))
        {
            font_x = SCREEN_WIDTH_PIXELS - font_x;
            font_flags |= PR_RIGHT;
        }

        font::print(PR_RTL_XFLIP, 2, 230, label, tr / 2, tg / 2, tb / 2);
        font::print(font_flags, font_x, 230, name, tr / 2, tg / 2, tb / 2);
        break;
    }
    case Menu::ed_music:
    {
        font::print(PR_2X | PR_CEN | PR_CJK_HIGH, -1, 65, loc::gettext("Map Music"), tr, tg, tb);

        font::print_wrap(PR_CEN | PR_CJK_LOW, -1, 85, loc::gettext("Current map music:"), tr, tg, tb);
        const char* songname;
        switch(cl.levmusic)
        {
        case 0:
            songname = loc::gettext("No background music");
            break;
        case Music_PUSHINGONWARDS:
            songname = loc::gettext("1: Pushing Onwards");
            break;
        case Music_POSITIVEFORCE:
            songname = loc::gettext("2: Positive Force");
            break;
        case Music_POTENTIALFORANYTHING:
            songname = loc::gettext("3: Potential for Anything");
            break;
        case Music_PASSIONFOREXPLORING:
            songname = loc::gettext("4: Passion for Exploring");
            break;
        case Music_PAUSE:
            songname = loc::gettext("N/A: Pause");
            break;
        case Music_PRESENTINGVVVVVV:
            songname = loc::gettext("5: Presenting VVVVVV");
            break;
        case Music_PLENARY:
            songname = loc::gettext("N/A: Plenary");
            break;
        case Music_PREDESTINEDFATE:
            songname = loc::gettext("6: Predestined Fate");
            break;
        case Music_POSITIVEFORCEREVERSED:
            songname = loc::gettext("N/A: ecroF evitisoP");
            break;
        case Music_POPULARPOTPOURRI:
            songname = loc::gettext("7: Popular Potpourri");
            break;
        case Music_PIPEDREAM:
            songname = loc::gettext("8: Pipe Dream");
            break;
        case Music_PRESSURECOOKER:
            songname = loc::gettext("9: Pressure Cooker");
            break;
        case Music_PACEDENERGY:
            songname = loc::gettext("10: Paced Energy");
            break;
        case Music_PIERCINGTHESKY:
            songname = loc::gettext("11: Piercing the Sky");
            break;
        case Music_PREDESTINEDFATEREMIX:
            songname = loc::gettext("N/A: Predestined Fate Remix");
            break;
        default:
            songname = loc::gettext("?: something else");
            break;
        }
        font::print_wrap(PR_CEN, -1, 120, songname, tr, tg, tb);
        break;
    }
    case Menu::ed_quit:
        font::print_wrap(PR_CEN, -1, 90, loc::gettext("Save before quitting?"), tr, tg, tb);
        break;
    case Menu::ed_font:
    {
        font::print(PR_2X | PR_CEN, -1, 30, loc::gettext("Level Font"), tr, tg, tb);
        font::print_wrap(PR_CEN, -1, 65, loc::gettext("Select the language in which the text in this level is written."), tr, tg, tb);

        const char* label = loc::gettext("Font: ");
        int len_label = font::len(0, label);
        const char* name = font::get_level_font_display_name();

        int font_x = 2 + len_label;
        uint32_t font_flags = PR_FONT_LEVEL;
        if (font::is_rtl(PR_FONT_INTERFACE))
        {
            font_x = SCREEN_WIDTH_PIXELS - font_x;
            font_flags |= PR_RIGHT;
        }

        font::print(PR_RTL_XFLIP, 2, 230, label, tr/2, tg/2, tb/2);
        font::print(font_flags, font_x, 230, name, tr/2, tg/2, tb/2);
        break;
    }
    default:
        break;
    }
}

static void draw_background_grid(void)
{
    for (int j = 0; j < 30; j++)
    {
        for (int i = 0; i < 40; i++)
        {
            if (i == 19 || i == 20 || j == 14 || j == 29)
            {
                // Major guidelines
                graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(32, 32, 32));
            }
            else if (i == 9 || i == 30 || j == 6 || j == 7 || j == 21 || j == 22)
            {
                // Minor guidelines
                graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(24, 24, 24));
            }
            else if (i % 4 == 0 || j % 4 == 0)
            {
                graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(16, 16, 16));
            }
            else
            {
                graphics.draw_rect(i * 8, j * 8, 7, 7, graphics.getRGB(8, 8, 8));
            }
        }
    }
}

static void draw_background(int warpdir)
{
    extern editorclass ed;

    switch (warpdir)
    {
    case 1:
        graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
        graphics.drawbackground(3);
        break;
    case 2:
        graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
        graphics.drawbackground(4);
        break;
    case 3:
        graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
        graphics.drawbackground(5);
        break;
    default:
        break;
    }
}

static void draw_edgeguide(const TileTypes type, const int x, const int y, const bool vertical)
{
    static const SDL_Color white = graphics.getRGB(255 - help.glow, 255, 255);
    static const SDL_Color red = graphics.getRGB(255 - help.glow, 127, 127);

    if (type != TileType_SOLID && type != TileType_SPIKE)
    {
        return;
    }

    if (vertical)
    {
        graphics.fill_rect(x, y, 8, 2, (type == TileType_SOLID) ? white : red);
    }
    else
    {
        graphics.fill_rect(x, y, 2, 8, (type == TileType_SOLID) ? white : red);
    }
}

static void draw_edgeguides(void)
{
    extern editorclass ed;

    const int global_x = ed.levx * 40;
    const int global_y = ed.levy * 30;

    // Draw edge-guides, so there's no room misalignments!

    for (int i = 0; i < 40; i++)
    {
        if (i < 30)
        {
            // Left edge
            draw_edgeguide(ed.get_abs_tile_type(global_x - 1, global_y + i, true), 0, i * 8, false);
            // Right edge
            draw_edgeguide(ed.get_abs_tile_type(global_x + 40, global_y + i, true), 318, i * 8, false);
        }

        // Top edge
        draw_edgeguide(ed.get_abs_tile_type(global_x + i, global_y - 1, true), i * 8, 0, true);
        // Bottom edge
        draw_edgeguide(ed.get_abs_tile_type(global_x + i, global_y + 30, true), i * 8, 238, true);
    }

    static const SDL_Color green = graphics.getRGB(127, 255 - help.glow, 127);

    // Horizontal gravity line edge-guides

    for (size_t i = 0; i < customentities.size(); ++i)
    {
        const CustomEntity* entity = &customentities[i];
        const bool is_horizontal_gravity_line = entity->t == 11 && entity->p1 == 0;
        if (!is_horizontal_gravity_line)
        {
            continue;
        }
        const int x = entity->p2 * 8;
        const int w = entity->p3;
        if (entity->ry != ed.levy)
        {
            continue;
        }
        if (entity->rx == POS_MOD(ed.levx - 1, cl.mapwidth)
            // It's to the left...
            && x + w >= SCREEN_WIDTH_PIXELS - 8)
        {
            // And touching the right edge!
            graphics.fill_rect(0, entity->y * 8, 2, 8, green);
        }
        else if (entity->rx == POS_MOD(ed.levx + 1, cl.mapwidth)
            // It's to the right...
            && x <= 0)
        {
            // And touching the left edge!
            graphics.fill_rect(SCREEN_WIDTH_PIXELS - 2, entity->y * 8, 2, 8, green);
        }
    }
}

static void update_entities(void)
{
    extern editorclass ed;

    for (size_t i = 0; i < customentities.size(); ++i)
    {
        CustomEntity* entity = &customentities[i];

        if (entity->rx != ed.levx || entity->ry != ed.levy)
        {
            // It's not in this room, so just continue
            continue;
        }

        bool grav_line = (entity->t == 11);
        bool warp_line = (entity->t == 50);

        if ((grav_line || warp_line) && entity->p4 != 1)
        {
            // If it's a grav line or a warp line, and it's not locked
            if ((grav_line && entity->p1 == 0) || (warp_line && entity->p1 >= 2))
            {
                /* Horizontal */
                int tx = entity->x;
                int tx2 = tx;
                int ty = entity->y;
                while (ed.lines_can_pass(tx, ty))
                {
                    --tx;
                }
                while (ed.lines_can_pass(tx2, ty))
                {
                    ++tx2;
                }
                ++tx;
                entity->p2 = tx;
                entity->p3 = (tx2 - tx) * 8;
            }
            else
            {
                /* Vertical */
                int tx = entity->x;
                int ty = entity->y;
                int ty2 = ty;
                while (ed.lines_can_pass(tx, ty))
                {
                    --ty;
                }
                while (ed.lines_can_pass(tx, ty2))
                {
                    ++ty2;
                }
                ++ty;
                entity->p2 = ty;
                entity->p3 = (ty2 - ty) * 8;
            }
        }
    }
}

static void draw_entities(void)
{
    extern editorclass ed;

    const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);

    //Draw entities
    obj.customplatformtile = game.customcol * 12;

    const int edent_under_cursor = ed.get_entity_at(ed.levx, ed.levy, ed.tilex, ed.tiley);

    // Special case for drawing gray entities
    bool custom_gray = room->tileset == 3 && room->tilecol == 6;

    // Draw entities backward to remain accurate with ingame
    for (int i = customentities.size() - 1; i >= 0; i--)
    {
        CustomEntity* entity = &customentities[i];

        // If the entity is in the current room, draw it
        if (entity->rx == ed.levx && entity->ry == ed.levy)
        {
            const int x = entity->x * 8;
            const int y = entity->y * 8;
            static const char arrows[] = "V^<>";

            switch (entity->t)
            {
            case 1: // Enemies
            {
                const int movement = entity->p1;

                if (custom_gray)
                {
                    ed.entcolreal = graphics.getcol(18);
                }

                graphics.draw_sprite(x, y, ed.get_enemy_tile(room->enemytype), ed.entcolreal);

                if (movement >= 0 && movement < 4)
                {
                    // If they have a basic movement type, draw an arrow to indicate direction
                    font::print(PR_FONT_8X8, x + 4, y + 4, std::string(1, arrows[movement]), 255, 255, 255 - help.glow);
                }

                graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 255));
                break;
            }
            case 2: // Conveyors & Platforms
            {
                const int movement = entity->p1;
                const short length = (movement == 7 || movement == 8) ? 8 : 4;
                const short glow = 255 - help.glow;

                for (int j = 0; j < length; j++) {
                    graphics.draw_grid_tile(custom_gray ? graphics.grphx.im_entcolours_tint : graphics.grphx.im_entcolours, obj.customplatformtile, x + (j * 8), y, 8, 8);
                }

                switch (movement)
                {
                case 0:
                case 1:
                case 2:
                case 3:
                    // If they have a basic movement type, draw an arrow to indicate direction
                    font::print(PR_FONT_8X8, x + 12, y, std::string(1, arrows[movement]), glow, glow, glow);
                    break;
                case 4:
                    // Always move right, stopping when hitting collision
                    font::print(PR_FONT_8X8, x + 8, y, ">I", glow, glow, glow);
                    break;
                case 5:
                    font::print(PR_FONT_8X8, x, y, ">>>>", glow, glow, glow);
                    break;
                case 6:
                    font::print(PR_FONT_8X8, x, y, "<<<<", glow, glow, glow);
                    break;
                case 7:
                    font::print(PR_FONT_8X8, x + 4, y, "> > > > ", glow, glow, glow);
                    break;
                case 8:
                    font::print(PR_FONT_8X8, x + 4, y, "< < < < ", glow, glow, glow);
                    break;
                }

                if (movement < 0)
                {
                    // Well, it's a negative type, so it'll just be still.
                    font::print(PR_FONT_8X8, x + 8, y, "[]", glow, glow, glow);
                }
                else if (movement > 8)
                {
                    // Invalid... draw a scary red X
                    font::print(PR_FONT_8X8, x + 12, y, "X", glow, 0, 0);
                }

                graphics.draw_rect(x, y, 8 * length, 8, graphics.getRGB(255, 255, 255));
                break;
            }
            case 3: // Disappearing Platforms
                for (int j = 0; j < 4; j++) {
                    graphics.draw_grid_tile(custom_gray ? graphics.grphx.im_entcolours_tint : graphics.grphx.im_entcolours, obj.customplatformtile, x + (j * 8), y, 8, 8);
                }

                font::print(PR_FONT_8X8, x, y, "////", 255 - help.glow, 255 - help.glow, 255 - help.glow);
                graphics.draw_rect(x, y, 32, 8, graphics.getRGB(255, 255, 255));
                break;
            case 9: // Shiny Trinkets
                graphics.draw_sprite(x, y, 22, 196, 196, 196);
                graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));
                break;
            case 10: // Checkpoints
                graphics.draw_sprite(x, y, 20 + entity->p1, 196, 196, 196);
                graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));
                break;
            case 11: // Gravity Lines
                // p2 is in tiles, and p3 is in pixels
                if (entity->p1 == 0)
                {
                    // Horizontal gravity line
                    const int left = entity->p2 * 8;
                    const int width = entity->p3;
                    graphics.fill_rect(left, y + 4, width, 1, graphics.getRGB(194, 194, 194));
                }
                else
                {
                    // Vertical gravity line
                    const int top = entity->p2 * 8;
                    const int height = entity->p3;
                    graphics.fill_rect(x + 3, top, 1, height, graphics.getRGB(194, 194, 194));
                }
                graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 164));
                break;
            case 13: // Warp Tokens
            {
                std::string text;

                graphics.draw_sprite(x, y, 18 + (ed.entframe % 2), 196, 196, 196);
                graphics.draw_rect(x, y, 16, 16, graphics.getRGB(255, 164, 164));

                if (i == edent_under_cursor)
                {
                    text = "(" + help.String(entity->p1 / 40 + 1) + "," + help.String(entity->p2 / 30 + 1) + ")";
                }
                else
                {
                    text = help.String(cl.findwarptoken(i));
                }

                font::print(PR_BOR | PR_CJK_HIGH, x, y - 8, text, 210, 210, 255);
                break;
            }
            case 15: // Crewmates
                graphics.draw_sprite(x - 4, y, 144, graphics.crewcolourreal(entity->p1));
                graphics.draw_rect(x, y, 16, 24, graphics.getRGB(164, 164, 164));
                break;
            case 16: // Start Point
            {
                const short labelcol = ed.entframe < 2 ? 255 : 196;

                if (entity->p1 == 0)  // Facing right
                {
                    graphics.draw_sprite(x - 4, y, 0, graphics.col_crewcyan);
                }
                else // Non-zero is facing left
                {
                    graphics.draw_sprite(x - 4, y, 3, graphics.col_crewcyan);
                }

                graphics.draw_rect(x, y, 16, 24, graphics.getRGB(255, 255, 164));
                font::print(PR_BOR | PR_CEN | PR_CJK_HIGH, x + 8, y - 8, loc::gettext("START"), labelcol, labelcol, labelcol);
                break;
            }
            case 17: // Roomtext
            {
                int width = 8;
                int height = 8;

                if (entity->scriptname.length() > 0)
                {
                    width = font::len(PR_FONT_LEVEL, entity->scriptname.c_str());
                    height = font::height(PR_FONT_LEVEL);
                }

                int rect_x = x;
                if (entity->p1)
                {
                    // RTL. The 8 is the size of a tile, not font width!
                    rect_x -= width - 8;
                }
                graphics.draw_rect(rect_x, y, width, height, graphics.getRGB(96, 96, 96));
                graphics.print_roomtext(x, y, entity->scriptname.c_str(), entity->p1);
                break;
            }
            case 18: // Terminals
            {
                int sprite = entity->p1;
                int corrected_y = y;

                // Not a boolean: just swapping 0 and 1, leaving the rest alone
                if (sprite == 0)
                {
                    sprite = 1; // Unflipped
                }
                else if (sprite == 1)
                {
                    sprite = 0; // Flipped;
                    corrected_y -= 8;
                }

                graphics.draw_sprite(x, corrected_y + 8, sprite + 16, 96, 96, 96);
                graphics.draw_rect(x, y, 16, 24, graphics.getRGB(164, 164, 164));
                if (i == edent_under_cursor)
                {
                    font::print(PR_FONT_LEVEL | PR_BOR | PR_CJK_HIGH, x, y - 8, entity->scriptname, 210, 210, 255);
                }
                break;
            }
            case 19: // Script Triggers
                graphics.draw_rect(x, y, entity->p1 * 8, entity->p2 * 8, graphics.getRGB(255, 164, 255));
                graphics.draw_rect(x, y, 8, 8, graphics.getRGB(255, 255, 255));
                if (i == edent_under_cursor)
                {
                    font::print(PR_FONT_LEVEL | PR_BOR | PR_CJK_HIGH, x, y - 8, entity->scriptname, 210, 210, 255);
                }
                break;
            case 50: // Warp Lines
                if (entity->p1 >= 2) // Horizontal
                {

                    int left = entity->p2;
                    int right = left + entity->p3 / 8;

                    graphics.draw_rect((left * 8), y + 1, (right - left) * 8, 6, graphics.getRGB(194, 255, 255));
                    graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 255));
                }
                else // Vertical
                {
                    int top = entity->p2;
                    int bottom = top + entity->p3 / 8;

                    graphics.draw_rect(x + 1, (top * 8), 6, (bottom - top) * 8, graphics.getRGB(194, 255, 255));
                    graphics.draw_rect(x, y, 8, 8, graphics.getRGB(164, 255, 255));
                }
                break;
            }
        }

        // Need to also check warp point destinations
        if (entity->t == 13 && ed.warp_token_entity != i &&
            entity->p1 / 40 == ed.levx && entity->p2 / 30 == ed.levy)
        {
            const int x = entity->p1 % 40 * 8;
            const int y = entity->p2 % 30 * 8;
            std::string text;

            graphics.draw_sprite(x, y, 18 + (ed.entframe % 2), 64, 64, 64);
            graphics.draw_rect((entity->p1 * 8) - (ed.levx * 40 * 8), (entity->p2 * 8) - (ed.levy * 30 * 8), 16, 16, graphics.getRGB(96, 64, 64));

            if (ed.tilex == x / 8 && ed.tiley == y / 8)
            {
                text = "(" + help.String(entity->rx + 1) + "," + help.String(entity->ry + 1) + ")";
            }
            else
            {
                text = help.String(cl.findwarptoken(i));
            }

            font::print(PR_BOR | PR_CJK_HIGH, x, y - 8, text, 190, 190, 225);
        }
    }
}

static void draw_ghosts(void)
{
    extern editorclass ed;

    //Draw ghosts (spooky!)
    if (game.ghostsenabled) {
        graphics.set_render_target(graphics.ghostTexture);
        graphics.set_blendmode(graphics.ghostTexture, SDL_BLENDMODE_BLEND);
        graphics.clear(0, 0, 0, 0);

        for (int i = 0; i < (int) ed.ghosts.size(); i++) {
            if (i <= ed.current_ghosts) { // We don't want all of them to show up at once :)
                if (ed.ghosts[i].rx != ed.levx || ed.ghosts[i].ry != ed.levy)
                    continue;
                SDL_Color ct = ed.ghosts[i].realcol;
                const int alpha = 3 * ct.a / 4;
                ct.a = (Uint8)alpha;
                graphics.draw_sprite(ed.ghosts[i].x, ed.ghosts[i].y, ed.ghosts[i].frame, ct);
            }
        }

        graphics.set_render_target(graphics.gameTexture);
        graphics.set_texture_alpha_mod(graphics.ghostTexture, 128);
        graphics.copy_texture(graphics.ghostTexture, NULL, NULL);
    }
}

static void adjust_box_coordinates(int x1, int y1, int x2, int y2, int* left, int* right, int* top, int* bottom)
{
    if (x1 < x2 + 8)
    {
        *right = x2 + 8;
        *left = x1;
    }
    else
    {
        *right = x1 + 8;
        *left = x2;
    }

    if (y1 < y2 + 8)
    {
        *bottom = y2 + 8;
        *top = y1;
    }
    else
    {
        *bottom = y1 + 8;
        *top = y2;
    }
}

static void draw_bounds(void)
{
    extern editorclass ed;

    const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);

    // Draw boundaries
    if (room->enemyx1 != 0 || room->enemyy1 != 0 || room->enemyx2 != 320 || room->enemyy2 != 240)
    {
        graphics.draw_rect(room->enemyx1, room->enemyy1, room->enemyx2 - room->enemyx1, room->enemyy2 - room->enemyy1, graphics.getRGB(255 - (help.glow / 2), 64, 64));
    }

    if (room->platx1 != 0 || room->platy1 != 0 || room->platx2 != 320 || room->platy2 != 240)
    {
        graphics.draw_rect(room->platx1, room->platy1, room->platx2 - room->platx1, room->platy2 - room->platy1, graphics.getRGB(64, 64, 255 - (help.glow / 2)));
    }

    if (ed.substate == EditorSubState_DRAW_BOX)
    {
        if (ed.box_corner == BoxCorner_FIRST)
        {
            graphics.draw_rect(ed.tilex * 8, ed.tiley * 8, 8, 8, graphics.getRGB(210 + help.glow / 2, 191 + help.glow, 255 - help.glow / 2));
            graphics.draw_rect((ed.tilex * 8) + 2, (ed.tiley * 8) + 2, 4, 4, graphics.getRGB(105 + help.glow / 4, 100 + help.glow / 2, 128 - help.glow / 4));
        }
        else
        {
            int left;
            int right;
            int top;
            int bottom;

            adjust_box_coordinates(ed.box_point.x, ed.box_point.y, ed.tilex * 8, ed.tiley * 8, &left, &right, &top, &bottom);

            graphics.draw_rect(left, top, right - left, bottom - top, graphics.getRGB(210 + help.glow / 2, 191 + help.glow, 255 - help.glow / 2));
            graphics.draw_rect(left + 2, top + 2, (right - left) - 4, (bottom - top) - 4, graphics.getRGB(105 + help.glow / 4, 100 + help.glow / 2, 128 - help.glow / 4));
        }
    }
}

static inline bool check_point(bool connected[SCREEN_HEIGHT_TILES][SCREEN_WIDTH_TILES], int x, int y)
{
    if (x < 0 || x >= SCREEN_WIDTH_TILES || y < 0 || y >= SCREEN_HEIGHT_TILES)
    {
        return false;
    }

    return connected[y][x];
}

static void draw_cursor(void)
{
    extern editorclass ed;

    static const SDL_Color blue = graphics.getRGB(32, 32, 200);

    const int x = ed.tilex * 8;
    const int y = ed.tiley * 8;

    if (ed.substate == EditorSubState_DRAW_BOX)
    {
        // Just draw a 1x1 cursor, overriding everything else
        graphics.draw_rect(x, y, 8, 8, blue);
        return;
    }


    switch (ed.current_tool)
    {
    case EditorTool_WALLS:
    case EditorTool_BACKING:
        // Modifiers!
        if (ed.f_modifier)
        {
            bool connected[SCREEN_HEIGHT_TILES][SCREEN_WIDTH_TILES];
            SDL_zeroa(connected);

            ed.get_tile_fill(ed.tilex, ed.tiley, cl.gettile(ed.levx, ed.levy, ed.tilex, ed.tiley), connected);

            graphics.set_color(blue);

            for (int i = 0; i < SCREEN_WIDTH_TILES * SCREEN_HEIGHT_TILES; i++)
            {
                const int x = i % SCREEN_WIDTH_TILES;
                const int y = i / SCREEN_WIDTH_TILES;

                if (!connected[y][x])
                    continue;

                bool top_left = true;
                bool top_right = true;
                bool bottom_left = true;
                bool bottom_right = true;

                if (!check_point(connected, x - 1, y))
                {
                    top_left = false;
                    bottom_left = false;
                    SDL_RenderDrawLine(gameScreen.m_renderer, x * 8, y * 8, x * 8, y * 8 + 7);
                }
                if (!check_point(connected, x + 1, y))
                {
                    top_right = false;
                    bottom_right = false;
                    SDL_RenderDrawLine(gameScreen.m_renderer, x * 8 + 7, y * 8, x * 8 + 7, y * 8 + 7);
                }
                if (!check_point(connected, x, y - 1))
                {
                    top_left = false;
                    top_right = false;
                    SDL_RenderDrawLine(gameScreen.m_renderer, x * 8, y * 8, x * 8 + 7, y * 8);
                }
                if (!check_point(connected, x, y + 1))
                {
                    bottom_left = false;
                    bottom_right = false;
                    SDL_RenderDrawLine(gameScreen.m_renderer, x * 8, y * 8 + 7, x * 8 + 7, y * 8 + 7);
                }

                if (!check_point(connected, x - 1, y - 1) && top_left)
                    SDL_RenderDrawPoint(gameScreen.m_renderer, x * 8, y * 8);
                if (!check_point(connected, x - 1, y + 1) && top_right)
                    SDL_RenderDrawPoint(gameScreen.m_renderer, x * 8, y * 8 + 7);
                if (!check_point(connected, x + 1, y - 1) && bottom_left)
                    SDL_RenderDrawPoint(gameScreen.m_renderer, x * 8 + 7, y * 8);
                if (!check_point(connected, x + 1, y + 1) && bottom_right)
                    SDL_RenderDrawPoint(gameScreen.m_renderer, x * 8 + 7, y * 8 + 7);
            }
        }
        else if (ed.b_modifier) graphics.draw_rect(x, 0, 8, 240, blue); // Vertical
        else if (ed.h_modifier) graphics.draw_rect(0, y, 320, 8, blue); // Horizontal
        else if (ed.v_modifier) graphics.draw_rect(x - 32, y - 32, 24 + 48, 24 + 48, blue); // 9x9
        else if (ed.c_modifier) graphics.draw_rect(x - 24, y - 24, 24 + 32, 24 + 32, blue); // 7x7
        else if (ed.x_modifier) graphics.draw_rect(x - 16, y - 16, 24 + 16, 24 + 16, blue); // 5x5
        else if (ed.z_modifier) graphics.draw_rect(x - 8, y - 8, 24, 24, blue); // 3x3
        SDL_FALLTHROUGH;
    case EditorTool_SPIKES:
    case EditorTool_GRAVITY_LINES:
    case EditorTool_ROOMTEXT:
    case EditorTool_SCRIPTS:
        // 1x1
        graphics.draw_rect(x, y, 8, 8, blue);
        break;
    case EditorTool_TRINKETS:
    case EditorTool_CHECKPOINTS:
    case EditorTool_ENEMIES:
    case EditorTool_WARP_TOKENS:
        // 2x2
        graphics.draw_rect(x, y, 16, 16, blue);
        break;
    case EditorTool_DISAPPEARING_PLATFORMS:
    case EditorTool_CONVEYORS:
    case EditorTool_MOVING_PLATFORMS:
        // 1x4 (platforms)
        graphics.draw_rect(x, y, 32, 8, blue);
        break;
    case EditorTool_WARP_LINES:
        // 1x1, but X if not on an edge (warp lines)
        if (ed.tilex == 0 || ed.tilex == 39 || ed.tiley == 0 || ed.tiley == 29)
        {
            graphics.draw_rect(x, y, 8, 8, blue);
        }
        else
        {
            font::print(PR_FONT_8X8, x, y, "X", 255, 0, 0);
        }
        break;
    case EditorTool_TERMINALS:
    case EditorTool_CREWMATES:
    case EditorTool_START_POINT:
        // 2x3
        graphics.draw_rect(x, y, 16, 24, blue);
        break;
    default:
        break;
    }
}

static void draw_tile_drawer(int tileset)
{
    extern editorclass ed;

    // Tile drawer for direct mode
    int t2 = 0;
    if (ed.direct_mode_drawer > 0)
    {
        if (ed.direct_mode_drawer <= 4)
        {
            t2 = graphics.lerp((4 - ed.direct_mode_drawer + 1) * 12, (4 - ed.direct_mode_drawer) * 12);
        }

        // Draw five lines of the editor
        const int temp = ed.direct_mode_tile - (ed.direct_mode_tile % 40) - 80;
        graphics.fill_rect(0, -t2, 320, 40, graphics.getRGB(0, 0, 0));
        graphics.fill_rect(0, -t2 + 40, 320, 2, graphics.getRGB(255, 255, 255));

        int texturewidth;
        int textureheight;

        if (graphics.query_texture(graphics.grphx.im_tiles, NULL, NULL, &texturewidth, &textureheight) != 0)
        {
            return;
        }

        const int numtiles = (int)(texturewidth / 8) * (textureheight / 8);

        for (int x = 0; x < SCREEN_WIDTH_TILES; x++)
        {
            for (int y = 0; y < 5; y++)
            {
                if (tileset == 0)
                {
                    graphics.drawtile(x * 8, (y * 8) - t2, (temp + numtiles + (y * SCREEN_WIDTH_TILES) + x) % numtiles);
                }
                else
                {
                    graphics.drawtile2(x * 8, (y * 8) - t2, (temp + numtiles + (y * SCREEN_WIDTH_TILES) + x) % numtiles);
                }
            }
        }

        // Highlight our little block
        graphics.draw_rect(((ed.direct_mode_tile % SCREEN_WIDTH_TILES) * 8) - 2, 16 - t2 - 2, 12, 12, graphics.getRGB(255 - help.glow, 196, 196));
        graphics.draw_rect(((ed.direct_mode_tile % SCREEN_WIDTH_TILES) * 8) - 1, 16 - t2 - 1, 10, 10, graphics.getRGB(0, 0, 0));
    }

    if (ed.direct_mode_drawer > 0 && t2 <= 30)
    {
        short labellen = 2 + font::len(0, loc::gettext("Tile:"));
        font::print(PR_BOR, 2, 45 - t2, loc::gettext("Tile:"), 196, 196, 255 - help.glow);
        font::print(PR_BOR, labellen + 16, 45 - t2, help.String(ed.direct_mode_tile), 196, 196, 255 - help.glow);
        graphics.fill_rect(labellen + 2, 44 - t2, 10, 10, graphics.getRGB(255 - help.glow, 196, 196));
        graphics.fill_rect(labellen + 3, 45 - t2, 8, 8, graphics.getRGB(0, 0, 0));

        if (tileset == 0)
        {
            graphics.drawtile(labellen + 3, 45 - t2, ed.direct_mode_tile);
        }
        else
        {
            graphics.drawtile2(labellen + 3, 45 - t2, ed.direct_mode_tile);
        }
    }
    else
    {
        short labellen = 2 + font::len(0, loc::gettext("Tile:"));
        int y = 2 + font::height(0);
        y = SDL_max(y, 12);
        font::print(PR_BOR, 2, y, loc::gettext("Tile:"), 196, 196, 255 - help.glow);
        font::print(PR_BOR, labellen + 16, y, help.String(ed.direct_mode_tile), 196, 196, 255 - help.glow);
        graphics.fill_rect(labellen + 2, y - 1, 10, 10, graphics.getRGB(255 - help.glow, 196, 196));
        graphics.fill_rect(labellen + 3, y, 8, 8, graphics.getRGB(0, 0, 0));

        if (tileset == 0)
        {
            graphics.drawtile(labellen + 3, 12, ed.direct_mode_tile);
        }
        else
        {
            graphics.drawtile2(labellen + 3, 12, ed.direct_mode_tile);
        }
    }
}

static void draw_box_placer()
{
    extern editorclass ed;

    std::string message;
    if (ed.box_corner == BoxCorner_FIRST)
    {
        switch (ed.box_type)
        {
        case BoxType_SCRIPT:
            message = loc::gettext("SCRIPT BOX: Click on the first corner");
            break;
        case BoxType_ENEMY:
            message = loc::gettext("ENEMY BOUNDS: Click on the first corner");
            break;
        case BoxType_PLATFORM:
            message = loc::gettext("PLATFORM BOUNDS: Click on the first corner");
            break;
        default:
            message = loc::gettext("Click on the first corner");
            break;
        }
    }
    else if (ed.box_corner == BoxCorner_LAST)
    {
        switch (ed.box_type)
        {
        case BoxType_SCRIPT:
            message = loc::gettext("SCRIPT BOX: Click on the last corner");
            break;
        case BoxType_ENEMY:
            message = loc::gettext("ENEMY BOUNDS: Click on the last corner");
            break;
        case BoxType_PLATFORM:
            message = loc::gettext("PLATFORM BOUNDS: Click on the last corner");
            break;
        default:
            message = loc::gettext("Click on the last corner");
            break;
        }
    }

    short lines;
    message = font::string_wordwrap(0, message, 312, &lines);
    short textheight = font::height(0) * lines;

    graphics.fill_rect(0, 238 - textheight, 320, 240, graphics.getRGB(32, 32, 32));
    graphics.fill_rect(0, 239 - textheight, 320, 240, graphics.getRGB(0, 0, 0));

    font::print_wrap(PR_RTL_XFLIP, 4, 240 - textheight, message.c_str(), 255, 255, 255, 8, 312);
}

static void draw_note()
{
    extern editorclass ed;

    if (ed.note_timer > 0 || ed.old_note_timer > 0)
    {
        short lines;
        std::string wrapped = font::string_wordwrap(0, ed.note, 304, &lines);
        short textheight = 8 + (lines - 1) * SDL_max(10, font::height(0));
        short banner_y = 120 - textheight / 2 - 5;

        float alpha = graphics.lerp(ed.old_note_timer, ed.note_timer);
        graphics.fill_rect(0, banner_y, 320, 10 + textheight, graphics.getRGB(92, 92, 92));
        graphics.fill_rect(0, banner_y + 1, 320, 8 + textheight, graphics.getRGB(0, 0, 0));
        font::print_wrap(PR_CEN, -1, banner_y + 5, wrapped.c_str(), 196 - ((45.0f - alpha) * 4), 196 - ((45.0f - alpha) * 4), 196 - ((45.0f - alpha) * 4));
    }
}

static void draw_toolbox(const char* coords)
{
    extern editorclass ed;

    // Draw the toolbox background
    graphics.fill_rect(0, 207, 320, 240, graphics.getRGB(32, 32, 32));
    graphics.fill_rect(0, 208, 320, 240, graphics.getRGB(0, 0, 0));

    // Draw all tools!
    const int tool_gap = 32;

    const int page = ed.current_tool / 10;
    const int max_pages = SDL_ceil(NUM_EditorTools / 10);
    const int page_tool_count = SDL_min(10, NUM_EditorTools - (page * 10));

    for (int i = 0; i < page_tool_count; i++)
    {
        const int current_tool_id = i + (page * 10);

        // First, draw the background
        graphics.fill_rect(4 + (i * tool_gap), 208, 20, 20, graphics.getRGB(32, 32, 32));

        // Draw the actual tool icon
        ed.draw_tool((EditorTools)current_tool_id, 4 + (i * tool_gap) + 2, 208 + 2);

        // Draw the tool outline...
        graphics.draw_rect(4 + (i * tool_gap), 208, 20, 20, (current_tool_id == ed.current_tool) ? graphics.getRGB(200, 200, 200) : graphics.getRGB(96, 96, 96));

        // ...and the hotkey
        const int col = current_tool_id == ed.current_tool ? 255 : 164;
        font::print(PR_FONT_8X8 | PR_BOR, 22 + i * tool_gap - 4, 224 - 4, ed.tool_key_chars[current_tool_id], col, col, col);
    }

    // Draw the page number, limit is 1 digit, so the max is 9 pages
    char buffer[4];
    SDL_snprintf(buffer, sizeof(buffer), "%d/%d", page + 1, max_pages + 1);
    font::print(PR_CJK_HIGH, 4, 232, buffer, 196, 196, 255 - help.glow);

    // Draw the button hint text
    char changetooltext[SCREEN_WIDTH_CHARS + 1];
    vformat_buf(changetooltext, sizeof(changetooltext),
        loc::gettext("{button1} and {button2} keys change tool"),
        "button1:str, button2:str",
        ",", "."
    );
    font::print(PR_CJK_HIGH | PR_RIGHT, 320, 232, changetooltext, 196, 196, 255 - help.glow);

    // Draw the current tool name
    char toolname_english[SCREEN_WIDTH_CHARS + 1];
    SDL_snprintf(toolname_english, sizeof(toolname_english), "%s: %s", ed.tool_key_chars[ed.current_tool], ed.tool_names[ed.current_tool]);

    const char* toolname = loc::gettext(toolname_english);

    int bgheight = 2 + font::height(0);
    int toolnamelen = font::len(0, toolname);
    graphics.fill_rect(0, 206 - bgheight, toolnamelen + 8, bgheight + 1, graphics.getRGB(32, 32, 32));
    graphics.fill_rect(0, 207 - bgheight, toolnamelen + 7, bgheight, graphics.getRGB(0, 0, 0));
    font::print(PR_BOR | PR_CJK_HIGH, 2, 198, toolname, 196, 196, 255 - help.glow);

    // And finally, draw the current room's coordinates
    int coordslen = font::len(0, coords);
    graphics.fill_rect(319 - coordslen - 8, 206 - bgheight, coordslen + 8, bgheight + 1, graphics.getRGB(32, 32, 32));
    graphics.fill_rect(320 - coordslen - 8, 207 - bgheight, coordslen + 8, bgheight, graphics.getRGB(0, 0, 0));
    font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 198, coords, 196, 196, 255 - help.glow);
}

static void draw_main_ui(void)
{
    extern editorclass ed;

    const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);

    char coords[8];
    SDL_snprintf(coords, sizeof(coords), "(%d,%d)", ed.levx + 1, ed.levy + 1);

    if (ed.toolbox_open)
    {
        draw_toolbox(coords);
    }
    else
    {
        if (room->roomname != "")
        {
            int font_height = font::height(PR_FONT_LEVEL);
            if (font_height <= 8)
            {
                graphics.footerrect.h = font_height + 2;
            }
            else
            {
                graphics.footerrect.h = font_height + 1;
            }
            graphics.footerrect.y = 240 - graphics.footerrect.h + ed.roomnamehide;

            graphics.set_blendmode(SDL_BLENDMODE_BLEND);
            graphics.fill_rect(&graphics.footerrect, graphics.getRGBA(0, 0, 0, graphics.translucentroomname ? 127 : 255));
            graphics.set_blendmode(SDL_BLENDMODE_NONE);

            font::print(PR_CEN | PR_BOR | PR_FONT_LEVEL | PR_CJK_LOW, -1, graphics.footerrect.y + 1 + ed.roomnamehide, room->roomname, 196, 196, 255 - help.glow);
            font::print(PR_BOR | PR_CJK_HIGH, 4, 232 - graphics.footerrect.h, loc::gettext("SPACE ^  SHIFT ^"), 196, 196, 255 - help.glow);
            font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 232 - graphics.footerrect.h, coords, 196, 196, 255 - help.glow);
        }
        else
        {
            font::print(PR_BOR | PR_CJK_HIGH, 4, 232, loc::gettext("SPACE ^  SHIFT ^"), 196, 196, 255 - help.glow);
            font::print(PR_BOR | PR_CJK_HIGH | PR_RIGHT, 316, 232, coords, 196, 196, 255 - help.glow);
        }
    }

    if (ed.help_open)
    {
        const char* shiftmenuoptions[] = {
            loc::gettext("F1: Change Tileset"),
            loc::gettext("F2: Change Colour"),
            loc::gettext("F3: Change Enemies"),
            loc::gettext("F4: Enemy Bounds"),
            loc::gettext("F5: Platform Bounds"),
            "",
            loc::gettext("F9: Reload Resources"),
            loc::gettext("F10: Direct Mode"),
            "",
            loc::gettext("W: Change Warp Dir"),
            loc::gettext("E: Change Roomname"),
        };
        int menuwidth = 0;
        for (size_t i = 0; i < SDL_arraysize(shiftmenuoptions); i++)
        {
            int len = font::len(0, shiftmenuoptions[i]);
            if (len > menuwidth)
                menuwidth = len;
        }

        int lineheight = font::height(0);
        lineheight = SDL_max(10, lineheight);
        int left_y = 230 - SDL_arraysize(shiftmenuoptions) * lineheight;

        graphics.draw_rect(0, left_y - 3, menuwidth + 17, 240, graphics.getRGB(64, 64, 64));
        graphics.fill_rect(0, left_y - 2, menuwidth + 16, 240, graphics.getRGB(0, 0, 0));
        for (size_t i = 0; i < SDL_arraysize(shiftmenuoptions); i++)
            font::print(0, 4, left_y + i * lineheight, shiftmenuoptions[i], 164, 164, 164);

        graphics.draw_rect(220, 207, 100, 60, graphics.getRGB(64, 64, 64));
        graphics.fill_rect(221, 208, 160, 60, graphics.getRGB(0, 0, 0));
        font::print(0, 224, 210, loc::gettext("S: Save Map"), 164, 164, 164);
        font::print(0, 224, 210 + lineheight, loc::gettext("L: Load Map"), 164, 164, 164);
    }
}

void editorclass::draw_tool(EditorTools tool, int x, int y)
{
    switch (tool)
    {
    case EditorTool_WALLS:
        graphics.drawtile(x, y, 83);
        graphics.drawtile(x + 8, y, 83);
        graphics.drawtile(x, y + 8, 83);
        graphics.drawtile(x + 8, y + 8, 83);
        break;
    case EditorTool_BACKING:
        graphics.drawtile(x, y, 680);
        graphics.drawtile(x + 8, y, 680);
        graphics.drawtile(x, y + 8, 680);
        graphics.drawtile(x + 8, y + 8, 680);
        break;
    case EditorTool_SPIKES:
        graphics.drawtile(x + 4, y + 4, 8);
        break;
    case EditorTool_TRINKETS:
        graphics.draw_sprite(x, y, 22, 196, 196, 196);
        break;
    case EditorTool_CHECKPOINTS:
        graphics.draw_sprite(x, y, 21, 196, 196, 196);
        break;
    case EditorTool_DISAPPEARING_PLATFORMS:
        graphics.drawtile(x, y + 4, 3);
        graphics.drawtile(x + 8, y + 4, 4);
        break;
    case EditorTool_CONVEYORS:
        graphics.drawtile(x, y + 4, 24);
        graphics.drawtile(x + 8, y + 4, 24);
        break;
    case EditorTool_MOVING_PLATFORMS:
        graphics.drawtile(x, y + 4, 1);
        graphics.drawtile(x + 8, y + 4, 1);
        break;
    case EditorTool_ENEMIES:
        graphics.draw_sprite(x, y, 78 + entframe, 196, 196, 196);
        break;
    case EditorTool_GRAVITY_LINES:
        graphics.fill_rect(x + 2, y + 8, 12, 1, graphics.getRGB(255, 255, 255));
        break;
    case EditorTool_ROOMTEXT:
        font::print(PR_FONT_8X8, x + 1, y, "AB", 196, 196, 255 - help.glow);
        font::print(PR_FONT_8X8, x + 1, y + 9, "CD", 196, 196, 255 - help.glow);
        break;
    case EditorTool_TERMINALS:
        graphics.draw_sprite(x, y, 17, 196, 196, 196);
        break;
    case EditorTool_SCRIPTS:
        graphics.draw_rect(x + 4, y + 4, 8, 8, graphics.getRGB(96, 96, 96));
        break;
    case EditorTool_WARP_TOKENS:
        graphics.draw_sprite(x, y, 18 + (entframe % 2), 196, 196, 196);
        break;
    case EditorTool_WARP_LINES:
        graphics.fill_rect(x + 6, y + 2, 4, 12, graphics.getRGB(255, 255, 255));
        break;
    case EditorTool_CREWMATES:
        graphics.draw_sprite(x, y, 186, graphics.col_crewblue);
        break;
    case EditorTool_START_POINT:
        graphics.draw_sprite(x, y, 184, graphics.col_crewcyan);
        break;
    default:
        break;
    }
}

void editorrender(void)
{
    extern editorclass ed;

    const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);

    graphics.clear();

    switch (ed.state)
    {
    case EditorState_DRAW:
        // Draw the editor guidelines
        draw_background_grid();

        // Draw the background, if any, over the guidelines
        draw_background(room->warpdir);

        graphics.drawmap();
        draw_edgeguides();

        draw_entities();
        draw_ghosts();

        draw_bounds();

        if (room->directmode == 1)
        {
            draw_tile_drawer(room->tileset);
        }

        draw_cursor();

        switch (ed.substate)
        {
        case EditorSubState_MAIN:
        {
            draw_main_ui();

            // Draw the current tool name
            char toolname_english[SCREEN_WIDTH_CHARS + 1];
            SDL_snprintf(toolname_english, sizeof(toolname_english), "%s: %s", ed.tool_key_chars[ed.current_tool], ed.tool_names[ed.current_tool]);

            const char* toolname = loc::gettext(toolname_english);

            font::print(PR_BOR, 2, 2, toolname, 196, 196, 255 - help.glow);

            break;
        }
        case EditorSubState_DRAW_BOX:
            draw_box_placer();
            break;
        case EditorSubState_DRAW_INPUT:
        {
            short lines;
            std::string wrapped = font::string_wordwrap(0, loc::gettext(ed.current_text_desc.c_str()), 312, &lines);
            short textheight = font::height(0) * lines + font::height(PR_FONT_LEVEL);

            graphics.fill_rect(0, 238 - textheight, 320, 240, graphics.getRGB(32, 32, 32));
            graphics.fill_rect(0, 239 - textheight, 320, 240, graphics.getRGB(0, 0, 0));
            font::print_wrap(PR_RTL_XFLIP, 4, 240 - textheight, wrapped.c_str(), 255, 255, 255, 8, 312);
            std::string input = key.keybuffer;
            if (ed.entframe < 2)
            {
                input += "_";
            }
            else
            {
                input += " ";
            }
            font::print(PR_CEN | PR_FONT_LEVEL | PR_CJK_HIGH, -1, 232, input, 196, 196, 255 - help.glow);
            break;
        }
        case EditorSubState_DRAW_WARPTOKEN:
        {
            // Placing warp token
            int textheight = font::height(0);
            graphics.fill_rect(0, 237 - textheight * 2, 320, 240, graphics.getRGB(32, 32, 32));
            graphics.fill_rect(0, 238 - textheight * 2, 320, 240, graphics.getRGB(0, 0, 0));
            font::print(PR_CJK_LOW | PR_RTL_XFLIP, 4, 240 - textheight * 2, loc::gettext("Left click to place warp destination"), 196, 196, 255 - help.glow);
            font::print(PR_CJK_LOW | PR_RTL_XFLIP, 4, 240 - textheight, loc::gettext("Right click to cancel"), 196, 196, 255 - help.glow);

            break;
        }
        default:
            break;
        }

        break;
    case EditorState_SCRIPTS:
        // Intended to look like Commodore 64's UI

        graphics.fill_rect(0, 0, 320, 240, graphics.getRGB(123, 111, 218));
        graphics.fill_rect(14, 16, 292, 208, graphics.getRGB(61, 48, 162));

        switch (ed.substate)
        {
        case EditorSubState_MAIN:
            font::print(PR_CEN, -1, 28, loc::gettext("**** VVVVVV SCRIPT EDITOR ****"), 123, 111, 218);
            font::print(PR_CEN, -1, 44, loc::gettext("PRESS ESC TO RETURN TO MENU"), 123, 111, 218);

            if (script.customscripts.empty())
            {
                font::print(PR_CEN, -1, 110, loc::gettext("NO SCRIPT IDS FOUND"), 123, 111, 218);
                font::print_wrap(PR_CEN, -1, 130, loc::gettext("CREATE A SCRIPT WITH EITHER THE TERMINAL OR SCRIPT BOX TOOLS"), 123, 111, 218, 10, 288);
                break;
            }
            for (int i = 0; i < 9; i++)
            {
                const int offset = ed.script_list_offset + i;
                const bool draw = offset < (int) script.customscripts.size();
                if (!draw)
                {
                    continue;
                }
                if (offset == ed.selected_script)
                {
                    std::string text_upper(loc::toupper(script.customscripts[script.customscripts.size() - 1 - offset].name));
                    char buffer[SCREEN_WIDTH_CHARS + 1];
                    vformat_buf(buffer, sizeof(buffer), loc::get_langmeta()->menu_select.c_str(), "label:str", text_upper.c_str());
                    font::print(PR_CEN, -1, 68 + (i * 16), buffer, 123, 111, 218);
                }
                else
                {
                    font::print(PR_CEN, -1, 68 + (i * 16), script.customscripts[script.customscripts.size() - 1 - offset].name, 123, 111, 218);
                }
            }
            break;
        case EditorSubState_SCRIPTS_EDIT:
        {
            // Draw the current script's name
            graphics.fill_rect(14, 226, 292, 12, graphics.getRGB(61, 48, 162));
            char namebuffer[SCREEN_WIDTH_CHARS + 1];
            vformat_buf(
                namebuffer, sizeof(namebuffer),
                loc::gettext("CURRENT SCRIPT: {name}"),
                "name:str",
                ed.current_script.c_str()
            );
            font::print(PR_CEN, -1, 228, namebuffer, 123, 111, 218);

            // Draw text
            int font_height = font::height(PR_FONT_LEVEL);
            for (int i = 0; i < ed.lines_visible; i++)
            {
                if (i + ed.script_offset < (int) ed.script_buffer.size())
                {
                    font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16, 20 + (i * font_height), ed.script_buffer[i + ed.script_offset], 123, 111, 218);
                }
            }

            // Draw cursor
            if (ed.entframe < 2)
            {
                font::print(PR_FONT_LEVEL | PR_CJK_LOW, 16 + font::len(PR_FONT_LEVEL, ed.script_buffer[ed.script_cursor_y].c_str()), 20 + ((ed.script_cursor_y - ed.script_offset) * font_height), "_", 123, 111, 218);
            }
            break;
        }
        default:
            break;
        }
        break;
    case EditorState_MENU:
    {
        if (!game.colourblindmode)
        {
            graphics.drawtowerbackground(graphics.titlebg);
        }
        else
        {
            graphics.clear();
        }

        int tr = SDL_clamp(graphics.titlebg.r - (help.glow / 4) - int(fRandom() * 4), 0, 255);
        int tg = SDL_clamp(graphics.titlebg.g - (help.glow / 4) - int(fRandom() * 4), 0, 255);
        int tb = SDL_clamp(graphics.titlebg.b - (help.glow / 4) - int(fRandom() * 4), 0, 255);

        editormenurender(tr, tg, tb);
        graphics.drawmenu(tr, tg, tb, game.currentmenuname);
        break;
    }
    default:
        break;
    }

    draw_note();

    graphics.drawfade();

    graphics.render();
}

void editorrenderfixed(void)
{
    extern editorclass ed;
    const RoomProperty* const room = cl.getroomprop(ed.levx, ed.levy);
    graphics.updatetitlecolours();

    game.customcol = cl.getlevelcol(room->tileset, room->tilecol) + 1;
    ed.entcol = cl.getenemycol(game.customcol);

    ed.entcolreal = graphics.getcol(ed.entcol);

    if (game.ghostsenabled)
    {
        for (size_t i = 0; i < ed.ghosts.size(); i++)
        {
            GhostInfo* ghost = &ed.ghosts[i];

            if ((int) i > ed.current_ghosts || ghost->rx != ed.levx || ghost->ry != ed.levy)
            {
                continue;
            }

            ghost->realcol = graphics.getcol(ghost->col);
        }

        ed.current_ghosts++;
        if (ed.z_modifier)
        {
            ed.current_ghosts++;
        }

        ed.current_ghosts = SDL_min(ed.current_ghosts, ed.ghosts.size());
    }

    switch (ed.state)
    {
    case EditorState_DRAW:
        switch (room->warpdir)
        {
        case 1:
            graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
            graphics.updatebackground(3);
            break;
        case 2:
            graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
            graphics.updatebackground(4);
            break;
        case 3:
            graphics.rcol = cl.getwarpbackground(ed.levx, ed.levy);
            graphics.updatebackground(5);
            break;
        default:
            break;
        }
        break;
    case EditorState_MENU:
        graphics.titlebg.bypos -= 2;
        graphics.titlebg.bscroll = -2;
        graphics.updatetowerbackground(graphics.titlebg);
        break;
    default:
        break;
    }

    if (cl.getroomprop(ed.levx, ed.levy)->directmode == 1)
    {
        if (ed.direct_mode_drawer > 0)
        {
            ed.direct_mode_drawer--;
        }
    }
    else
    {
        ed.direct_mode_drawer = 0;
    }

    if (cl.getroomprop(ed.levx, ed.levy)->roomname != "")
    {
        if (ed.tiley < 28)
        {
            if (ed.roomnamehide > 0)
            {
                ed.roomnamehide--;
            }
        }
        else
        {
            if (ed.roomnamehide < 14)
            {
                ed.roomnamehide++;
            }
        }
    }
    else
    {
        if (ed.tiley < 28)
        {
            ed.roomnamehide = 0;
        }
        else
        {
            ed.roomnamehide = 14;
        }
    }
}

static void input_submitted(void)
{
    extern editorclass ed;

    *ed.current_text_ptr = key.keybuffer;

    ed.help_open = false;
    ed.shiftkey = false;

    bool reset_text_mode = true;

    key.disabletextentry();

    ed.substate = EditorSubState_MAIN;

    switch (ed.current_text_mode)
    {
    case TEXT_GOTOROOM:
    {
        char coord_x[16];
        char coord_y[16];

        const char* comma = SDL_strchr(key.keybuffer.c_str(), ',');

        bool valid_input = comma != NULL;

        if (valid_input)
        {
            SDL_strlcpy(
                coord_x,
                key.keybuffer.c_str(),
                SDL_min((size_t) (comma - key.keybuffer.c_str() + 1), sizeof(coord_x))
            );
            SDL_strlcpy(coord_y, &comma[1], sizeof(coord_y));

            valid_input = is_number(coord_x) && is_number(coord_y);
        }

        if (!valid_input)
        {
            ed.show_note(loc::gettext("ERROR: Invalid format"));
            break;
        }

        ed.levx = SDL_clamp(help.Int(coord_x) - 1, 0, cl.mapwidth - 1);
        ed.levy = SDL_clamp(help.Int(coord_y) - 1, 0, cl.mapheight - 1);
        graphics.foregrounddrawn = false;
        graphics.backgrounddrawn = false;
        break;
    }
    case TEXT_LOAD:
    {
        std::string loadstring = ed.filename + ".vvvvvv";
        if (cl.load(loadstring))
        {
            // don't use filename, it has the full path
            char buffer[3 * SCREEN_WIDTH_CHARS + 1];
            vformat_buf(buffer, sizeof(buffer), loc::gettext("Loaded map: {filename}.vvvvvv"), "filename:str", ed.filename.c_str());
            ed.show_note(buffer);
        }
        else
        {
            ed.show_note(loc::gettext("ERROR: Could not load level"));
        }
        graphics.foregrounddrawn = false;
        graphics.backgrounddrawn = false;
        ed.substate = EditorSubState_MAIN;
        break;
    }
    case TEXT_SAVE:
    {
        std::string savestring = ed.filename + ".vvvvvv";
        if (cl.save(savestring))
        {
            char buffer[3 * SCREEN_WIDTH_CHARS + 1];
            vformat_buf(buffer, sizeof(buffer), loc::gettext("Saved map: {filename}.vvvvvv"), "filename:str", ed.filename.c_str());
            ed.show_note(buffer);
        }
        else
        {
            ed.show_note(loc::gettext("ERROR: Could not save level!"));
            ed.saveandquit = false;
        }
        ed.note_timer = 45;

        if (ed.saveandquit)
        {
            graphics.fademode = FADE_START_FADEOUT; /* quit editor */
        }
        break;
    }
    case TEXT_SCRIPT:
        ed.clear_script_buffer();
        if (!ed.script_exists(key.keybuffer))
        {
            ed.create_script(key.keybuffer);
        }
        break;
    case TEXT_TITLE:
        cl.title = key.keybuffer;
        if (cl.title == "")
        {
            cl.title = "Untitled Level";
        }
        break;
    case TEXT_CREATOR:
        cl.creator = key.keybuffer;
        if (cl.creator == "")
        {
            cl.creator = "Unknown";
        }
        break;
    case TEXT_WEBSITE:
        cl.website = key.keybuffer;
        break;
    case TEXT_DESC1:
        cl.Desc1 = key.keybuffer;
        ed.current_text_mode = TEXT_DESC2;
        ed.substate = EditorSubState_MENU_INPUT;
        reset_text_mode = false;
        key.enabletextentry();
        ed.current_text_ptr = &(key.keybuffer);
        key.keybuffer = cl.Desc2;
        break;
    case TEXT_DESC2:
        cl.Desc2 = key.keybuffer;

        if (font::height(PR_FONT_LEVEL) <= 10)
        {
            ed.current_text_mode = TEXT_DESC3;
            key.enabletextentry();
            ed.substate = EditorSubState_MENU_INPUT;
            reset_text_mode = false;
            ed.current_text_ptr = &(key.keybuffer);
            key.keybuffer = cl.Desc3;
        }
        else
        {
            cl.Desc3 = "";
        }

        break;
    case TEXT_DESC3:
        cl.Desc3 = key.keybuffer;
        break;
    default:
        break;
    }

    if (reset_text_mode)
    {
        ed.current_text_mode = TEXT_NONE;
    }
}

void editorlogic(void)
{
    extern editorclass ed;

    //Misc
    help.updateglow();

    ed.entframedelay--;
    if (ed.entframedelay <= 0)
    {
        ed.entframe = (ed.entframe + 1) % 4;
        ed.entframedelay = 8;
    }

    ed.old_note_timer = ed.note_timer;
    ed.note_timer = SDL_max(ed.note_timer - 1, 0);

    update_entities();

    if (graphics.fademode == FADE_FULLY_BLACK)
    {
        //Return to game
        graphics.titlebg.colstate = 10;
        map.nexttowercolour();
        game.quittomenu();
        music.play(Music_PRESENTINGVVVVVV); // should be before game.quittomenu()
    }
}

void editorclass::add_entity(int rx, int ry, int xp, int yp, int tp, int p1, int p2, int p3, int p4, int p5, int p6)
{
    CustomEntity entity;

    entity.rx = rx;
    entity.ry = ry;
    entity.x = xp;
    entity.y = yp;
    entity.t = tp;
    entity.p1 = p1;
    entity.p2 = p2;
    entity.p3 = p3;
    entity.p4 = p4;
    entity.p5 = p5;
    entity.p6 = p6;
    entity.scriptname = "";

    customentities.push_back(entity);
}

void editorclass::remove_entity(int t)
{
    customentities.erase(customentities.begin() + t);
}

int editorclass::get_entity_at(int rx, int ry, int xp, int yp)
{
    for (size_t i = 0; i < customentities.size(); i++)
    {
        const CustomEntity* entity = &customentities[i];
        if (entity->rx == rx && entity->ry == ry &&
            entity->x == xp && entity->y == yp)
        {
            return i;
        }
    }
    return -1;
}

static void set_tile_interpolated(const int x1, const int x2, const int y1, const int y2, const int tile)
{
    extern editorclass ed;

    // draw a line between (x1, y1) and (x2, y2)

    const int dx = x2 - x1;
    const int dy = y2 - y1;

    const int steps = SDL_max(SDL_abs(dx), SDL_abs(dy));

    if (steps == 0)
    {
        ed.set_tile(x1, y1, tile);
        return;
    }

    for (int i = 0; i <= steps; i++)
    {
        const int x = x1 + (dx * i) / steps;
        const int y = y1 + (dy * i) / steps;

        ed.set_tile(x, y, tile);
    }
}

void editorclass::get_tile_fill(int tilex, int tiley, int tile, bool connected[SCREEN_HEIGHT_TILES][SCREEN_WIDTH_TILES])
{
    if (tilex < 0 || tilex >= SCREEN_WIDTH_TILES || tiley < 0 || tiley >= SCREEN_HEIGHT_TILES)
    {
        // It's out of bounds
        return;
    }

    if (connected[tiley][tilex])
    {
        // We've already visited this tile
        return;
    }

    if (cl.gettile(levx, levy, tilex, tiley) != tile)
    {
        // It's not the same tile
        return;
    }

    // Yep, this is connected!
    connected[tiley][tilex] = true;

    // Check surrounding 4 tiles
    get_tile_fill(tilex - 1, tiley, tile, connected);
    get_tile_fill(tilex + 1, tiley, tile, connected);
    get_tile_fill(tilex, tiley - 1, tile, connected);
    get_tile_fill(tilex, tiley + 1, tile, connected);
}

void editorclass::handle_tile_placement(const int tile)
{
    int range = 1;

    if (f_modifier)
    {
        bool connected[SCREEN_HEIGHT_TILES][SCREEN_WIDTH_TILES];
        SDL_zeroa(connected);

        get_tile_fill(tilex, tiley, cl.gettile(levx, levy, tilex, tiley), connected);

        for (int i = 0; i < SCREEN_WIDTH_TILES * SCREEN_HEIGHT_TILES; i++)
        {
            const int x = i % SCREEN_WIDTH_TILES;
            const int y = i / SCREEN_WIDTH_TILES;

            if (connected[y][x])
            {
                set_tile(x, y, tile);
            }
        }
        return;
    }
    else if (b_modifier)
    {
        // Vertical line
        for (int i = 0; i < SCREEN_HEIGHT_TILES; i++)
        {
            set_tile_interpolated(old_tilex, tilex, i, i, tile);
        }
        return;
    }
    else if (h_modifier)
    {
        // Horizontal line
        for (int i = 0; i < SCREEN_WIDTH_TILES; i++)
        {
            set_tile_interpolated(i, i, old_tiley, tiley, tile);
        }
        return;
    }
    else if (v_modifier)
    {
        range = 4;
    }
    else if (c_modifier)
    {
        range = 3;
    }
    else if (x_modifier)
    {
        range = 2;
    }
    else if (z_modifier)
    {
        range = 1;
    }
    else
    {
        set_tile_interpolated(old_tilex, tilex, old_tiley, tiley, tile);
        return;
    }

    for (int i = -range; i <= range; i++)
    {
        for (int j = -range; j <= range; j++)
        {
            set_tile_interpolated(old_tilex + i, tilex + i, old_tiley + j, tiley + j, tile);
        }
    }
}

void editorclass::tool_remove()
{
    switch (current_tool)
    {
    case EditorTool_WALLS:
    case EditorTool_BACKING:
        handle_tile_placement(0);
        break;
    case EditorTool_SPIKES:
        set_tile_interpolated(old_tilex, tilex, old_tiley, tiley, 0);
        break;
    default:
        break;
    }

    for (size_t i = 0; i < customentities.size(); i++)
    {
        const CustomEntity* entity = &customentities[i];
        if (entity->rx == levx && entity->ry == levy &&
            entity->x == tilex && entity->y == tiley)
        {
            remove_entity(i);
        }
    }
}

void editorclass::entity_clicked(const int index)
{
    CustomEntity* entity = &customentities[index];

    lclickdelay = 1;

    switch (entity->t)
    {
    case 1:
        // Enemies
        entity->p1 = (entity->p1 + 1) % 4;
        break;
    case 2:
    {
        // Moving Platforms and Conveyors
        const bool conveyor = entity->p1 >= 5;
        entity->p1++;
        if (conveyor)
        {
            entity->p1 = (entity->p1 - 5) % 4 + 5;
        }
        else
        {
            entity->p1 %= 4;
        }
        break;
    }
    case 10:
        // Checkpoints
        // If it's not textured as a checkpoint, then just leave it be
        if (entity->p1 == 0 || entity->p1 == 1)
        {
            entity->p1 = (entity->p1 + 1) % 2;
        }
        break;
    case 11:
    case 16:
        // Gravity Lines, Start Point
        entity->p1 = (entity->p1 + 1) % 2;
        break;
    case 15:
        // Crewmates
        entity->p1 = (entity->p1 + 1) % 6;
        break;
    case 17:
        // Roomtext
        get_input_line(TEXT_ROOMTEXT, "Enter roomtext:", &entity->scriptname);
        text_entity = index;
        break;
    case 18:
        // Terminals
        if (entity->p1 == 0 || entity->p1 == 1)
        {
            // Flip the terminal, but if it's not textured as a terminal leave it alone
            entity->p1 = (entity->p1 + 1) % 2;
        }
        SDL_FALLTHROUGH;
    case 19:
        // Script Boxes (and terminals)
        get_input_line(TEXT_SCRIPT, "Enter script name:", &entity->scriptname);
        text_entity = index;
        break;
    }
}

void editorclass::tool_place()
{
    const int entity = get_entity_at(levx, levy, tilex, tiley);
    if (entity != -1)
    {
        entity_clicked(entity);
        return;
    }

    switch (current_tool)
    {
    case EditorTool_WALLS:
    case EditorTool_BACKING:
    {
        int tile = 0;

        if (cl.getroomprop(levx, levy)->directmode >= 1)
        {
            tile = direct_mode_tile;
        }
        else if (current_tool == EditorTool_WALLS)
        {
            tile = 1;
        }
        else if (current_tool == EditorTool_BACKING)
        {
            tile = 2;
        }

        handle_tile_placement(tile);
        break;
    }
    case EditorTool_SPIKES:
        set_tile_interpolated(old_tilex, tilex, old_tiley, tiley, 8);
        break;
    case EditorTool_TRINKETS:
        if (cl.numtrinkets() < 100)
        {
            add_entity(levx, levy, tilex, tiley, 9);
            lclickdelay = 1;
        }
        else
        {
            show_note(loc::gettext("ERROR: Max number of trinkets is 100"));
        }
        break;
    case EditorTool_CHECKPOINTS:
        add_entity(levx, levy, tilex, tiley, 10, 1);
        lclickdelay = 1;
        break;
    case EditorTool_DISAPPEARING_PLATFORMS:
        add_entity(levx, levy, tilex, tiley, 3);
        lclickdelay = 1;
        break;
    case EditorTool_CONVEYORS:
        add_entity(levx, levy, tilex, tiley, 2, 5);
        lclickdelay = 1;
        break;
    case EditorTool_MOVING_PLATFORMS:
        add_entity(levx, levy, tilex, tiley, 2, 0);
        lclickdelay = 1;
        break;
    case EditorTool_ENEMIES:
        add_entity(levx, levy, tilex, tiley, 1, 0);
        lclickdelay = 1;
        break;
    case EditorTool_GRAVITY_LINES:
        add_entity(levx, levy, tilex, tiley, 11, 0);
        lclickdelay = 1;
        break;
    case EditorTool_ROOMTEXT:
        lclickdelay = 1;
        text_entity = customentities.size();
        add_entity(levx, levy, tilex, tiley, 17, cl.rtl ? 1 : 0);
        get_input_line(TEXT_ROOMTEXT, "Enter roomtext:", &(customentities[text_entity].scriptname));
        break;
    case EditorTool_TERMINALS:
        lclickdelay = 1;
        text_entity = customentities.size();
        add_entity(levx, levy, tilex, tiley, 18, 0);
        get_input_line(TEXT_SCRIPT, "Enter script name:", &(customentities[text_entity].scriptname));
        break;
    case EditorTool_SCRIPTS:
        substate = EditorSubState_DRAW_BOX;
        box_corner = BoxCorner_LAST;
        box_type = BoxType_SCRIPT;
        box_point.x = tilex * 8;
        box_point.y = tiley * 8;

        lclickdelay = 1;
        break;
    case EditorTool_WARP_TOKENS:
        substate = EditorSubState_DRAW_WARPTOKEN;
        warp_token_entity = customentities.size();
        add_entity(levx, levy, tilex, tiley, 13);
        lclickdelay = 1;
        break;
    case EditorTool_WARP_LINES:
        //Warp lines
        if (tilex == 0)
        {
            add_entity(levx, levy, tilex, tiley, 50, 0);
        }
        else if (tilex == 39)
        {
            add_entity(levx, levy, tilex, tiley, 50, 1);
        }
        else if (tiley == 0)
        {
            add_entity(levx, levy, tilex, tiley, 50, 2);
        }
        else if (tiley == 29)
        {
            add_entity(levx, levy, tilex, tiley, 50, 3);
        }
        else
        {
            show_note(loc::gettext("ERROR: Warp lines must be on edges"));
        }
        lclickdelay = 1;
        break;
    case EditorTool_CREWMATES:
        if (cl.numcrewmates() < 100)
        {
            add_entity(levx, levy, tilex, tiley, 15, int(fRandom() * 6));
            lclickdelay = 1;
        }
        else
        {
            show_note(loc::gettext("ERROR: Max number of crewmates is 100"));
        }
        break;
    case EditorTool_START_POINT:
        //If there is another start point, destroy it
        for (size_t i = 0; i < customentities.size(); i++)
        {
            if (customentities[i].t == 16)
            {
                remove_entity(i);
                i--;
            }
        }
        add_entity(levx, levy, tilex, tiley, 16, 0);
        lclickdelay = 1;
        break;
    default:
        break;
    }
}

static void creategameoptions(void)
{
    game.createmenu(Menu::options);
}

static void nextbgcolor(void)
{
    map.nexttowercolour();
}

static void editormenuactionpress(void)
{
    extern editorclass ed;

    switch (game.currentmenuname)
    {
    case Menu::ed_desc:
        switch (game.currentmenuoption)
        {
        case 0:
        {
            const bool title_is_gettext = translate_title(cl.title);

            ed.current_text_mode = TEXT_TITLE;
            ed.substate = EditorSubState_MENU_INPUT;
            key.enabletextentry();
            ed.current_text_ptr = &(key.keybuffer);

            if (title_is_gettext)
            {
                key.keybuffer = "";
            }
            else
            {
                key.keybuffer = cl.title;
            }
            break;
        }
        case 1:
        {
            const bool creator_is_gettext = translate_creator(cl.creator);

            ed.current_text_mode = TEXT_CREATOR;
            ed.substate = EditorSubState_MENU_INPUT;
            key.enabletextentry();
            ed.current_text_ptr = &(key.keybuffer);
            if (creator_is_gettext)
            {
                key.keybuffer = "";
            }
            else
            {
                key.keybuffer = cl.creator;
            }
            break;
        }
        case 2:
            ed.current_text_mode = TEXT_DESC1;
            ed.substate = EditorSubState_MENU_INPUT;
            key.enabletextentry();
            ed.current_text_ptr = &(key.keybuffer);
            key.keybuffer = cl.Desc1;
            break;
        case 3:
            ed.current_text_mode = TEXT_WEBSITE;
            ed.substate = EditorSubState_MENU_INPUT;
            key.enabletextentry();
            ed.current_text_ptr = &(key.keybuffer);
            key.keybuffer=cl.website;
            break;
        case 4:
            game.createmenu(Menu::ed_font);
            map.nexttowercolour();
            break;
        case 5:
            game.returnmenu();
            map.nexttowercolour();
            break;
        }
        music.playef(Sound_VIRIDIAN);
        break;
    case Menu::ed_settings:
        switch (game.currentmenuoption)
        {
        case 0:
            //Change level description stuff
            music.playef(Sound_VIRIDIAN);
            game.createmenu(Menu::ed_desc);
            map.nexttowercolour();
            break;
        case 1:
            //Enter script editormode
            music.playef(Sound_VIRIDIAN);

            ed.state = EditorState_SCRIPTS;
            ed.substate = EditorSubState_MAIN;

            ed.clear_script_buffer();
            key.keybuffer = "";
            ed.script_list_offset = 0;
            ed.selected_script = 0;

            ed.script_cursor_y = 0;
            ed.script_cursor_x = 0;
            ed.script_offset = 0;
            ed.lines_visible = 200 / font::height(PR_FONT_LEVEL);
            break;
        case 2:
            music.playef(Sound_VIRIDIAN);
            game.createmenu(Menu::ed_music);
            map.nexttowercolour();
            if(cl.levmusic>0) music.play(cl.levmusic);
            break;
        case 3:
            music.playef(Sound_VIRIDIAN);
            game.ghostsenabled = !game.ghostsenabled;
            break;
        case 4:
            //Load level
            map.nexttowercolour();

            ed.keydelay = 6;
            ed.get_input_line(TEXT_LOAD, "Enter map filename to load:", &(ed.filename));
            game.mapheld = true;
            graphics.backgrounddrawn = false;
            break;
        case 5:
            //Save level
            map.nexttowercolour();

            ed.keydelay = 6;
            ed.get_input_line(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
            game.mapheld = true;
            graphics.backgrounddrawn = false;
            break;
        case 6:
            /* Game options */
            music.playef(Sound_VIRIDIAN);
            game.gamestate = TITLEMODE;
            game.ingame_titlemode = true;
            game.ingame_editormode = true;

            DEFER_CALLBACK(creategameoptions);
            DEFER_CALLBACK(nextbgcolor);
            break;
        default:
            music.playef(Sound_VIRIDIAN);
            game.createmenu(Menu::ed_quit);
            map.nexttowercolour();
            break;
        }
        break;
    case Menu::ed_music:
        switch (game.currentmenuoption)
        {
        case 0:
        case 1:
            switch (game.currentmenuoption)
            {
            case 0:
                cl.levmusic++;
                break;
            case 1:
                cl.levmusic--;
                break;
            }

            cl.levmusic = POS_MOD(cl.levmusic, 16);

            if (cl.levmusic > 0)
            {
                music.play(cl.levmusic);
            }
            else
            {
                music.haltdasmusik();
            }

            music.playef(Sound_VIRIDIAN);
            break;
        case 2:
            music.playef(Sound_VIRIDIAN);
            music.fadeout();
            game.returnmenu();
            map.nexttowercolour();
            break;
        }
        break;
    case Menu::ed_quit:
        switch (game.currentmenuoption)
        {
        case 0:
            //Saving and quit
            ed.saveandquit = true;

            map.nexttowercolour();

            ed.keydelay = 6;
            ed.get_input_line(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
            game.mapheld = true;
            graphics.backgrounddrawn = false;
            break;
        case 1:
            //Quit without saving
            music.playef(Sound_VIRIDIAN);
            music.fadeout();
            graphics.fademode = FADE_START_FADEOUT;
            graphics.backgrounddrawn = false;
            break;
        case 2:
            //Go back to editor
            music.playef(Sound_VIRIDIAN);
            game.returnmenu();
            map.nexttowercolour();
            break;
        }
        break;
    case Menu::ed_font:
    {
        uint8_t idx_selected = font::font_idx_options[game.currentmenuoption];
        cl.level_font_name = font::get_main_font_name(idx_selected);
        if (idx_selected == loc::get_langmeta()->font_idx)
        {
            loc::new_level_font = "";
        }
        else
        {
            loc::new_level_font = cl.level_font_name;
        }
        font::set_level_font(cl.level_font_name.c_str());
        music.playef(Sound_VIRIDIAN);
        game.returnmenu();
        map.nexttowercolour();
        game.savestatsandsettings_menu();
        break;
    }
    default:
        break;
    }
}

static void start_at_checkpoint(void)
{
    extern editorclass ed;

    // Scan the room for a start point or a checkpoint, the start point taking priority
    int testeditor = -1;
    bool startpoint = false;

    for (size_t i = 0; i < customentities.size(); i++)
    {
        startpoint = customentities[i].t == 16;
        const bool is_startpoint_or_checkpoint = startpoint ||
            customentities[i].t == 10;
        if (!is_startpoint_or_checkpoint)
        {
            continue;
        }

        const bool in_room = customentities[i].rx == ed.levx &&
            customentities[i].ry == ed.levy;
        if (!in_room)
        {
            continue;
        }

        if (startpoint)
        {
            // Oh, there's a start point! Let's use this.
            testeditor = i;
            break;
        }
        else if (testeditor == -1)
        {
            // This is the first thing we found, so let's use it (unless we find a start point later)
            testeditor = i;
        }
    }

    if (testeditor == -1)
    {
        ed.show_note(loc::gettext("ERROR: No checkpoint to spawn at"));
    }
    else
    {
        ed.current_ghosts = 0;

        game.edsavex = (customentities[testeditor].x) * 8 - 4;
        game.edsavey = customentities[testeditor].y * 8;
        game.edsaverx = 100 + customentities[testeditor].rx;
        game.edsavery = 100 + customentities[testeditor].ry;

        if (!startpoint)
        {
            // Checkpoint spawn
            if (customentities[testeditor].p1 == 0) // NOT a bool check!
            {
                game.edsavegc = 1;
                game.edsavey -= 2;
            }
            else
            {
                game.edsavegc = 0;
                game.edsavey -= 7;
            }
            game.edsavedir = 0;
        }
        else
        {
            // Start point spawn
            game.edsavegc = 0;
            game.edsavey++;
            game.edsavedir = 1 - customentities[testeditor].p1;
        }

        music.haltdasmusik();
        ed.return_message_timer = 1000; // Let's start it higher than 255 since it gets clamped
        ed.old_return_message_timer = 1000;
        script.startgamemode(Start_EDITORPLAYTESTING);
    }
}

static void handle_draw_input()
{
    extern editorclass ed;

    bool shift_down = key.keymap[SDLK_LSHIFT] || key.keymap[SDLK_RSHIFT];

    if (shift_down && !ed.shiftkey)
    {
        ed.shiftkey = true;
        ed.help_open = !ed.help_open;
    }
    else if (!shift_down)
    {
        ed.shiftkey = false;
    }

    if (ed.keydelay > 0)
    {
        ed.keydelay--;
    }
    else
    {
        if (key.keymap[SDLK_F1])
        {
            ed.switch_tileset(shift_down);
            ed.keydelay = 6;
        }
        if (key.keymap[SDLK_F2])
        {
            ed.switch_tilecol(shift_down);
            ed.keydelay = 6;
        }
        if (key.keymap[SDLK_F3])
        {
            ed.switch_enemy(shift_down);
            ed.keydelay = 6;
        }
        if (key.keymap[SDLK_F4])
        {
            ed.keydelay = 6;
            ed.substate = EditorSubState_DRAW_BOX;
            ed.box_corner = BoxCorner_FIRST;
            ed.box_type = BoxType_ENEMY;
        }
        if (key.keymap[SDLK_F5])
        {
            ed.keydelay = 6;
            ed.substate = EditorSubState_DRAW_BOX;
            ed.box_corner = BoxCorner_FIRST;
            ed.box_type = BoxType_PLATFORM;
        }
        if (key.keymap[SDLK_F10])
        {
            if (cl.getroomprop(ed.levx, ed.levy)->directmode == 1)
            {
                cl.setroomdirectmode(ed.levx, ed.levy, 0);
                ed.show_note(loc::gettext("Direct Mode Disabled"));
                ed.clamp_tilecol(ed.levx, ed.levy, true);
            }
            else
            {
                cl.setroomdirectmode(ed.levx, ed.levy, 1);
                ed.show_note(loc::gettext("Direct Mode Enabled"));
            }
            graphics.backgrounddrawn = false;

            ed.updatetiles = true;
            ed.keydelay = 6;
        }

        for (int i = 0; i < NUM_EditorTools; i++)
        {
            if (key.keymap[ed.tool_keys[i]] &&
                ((shift_down && ed.tool_requires_shift[i]) ||
                    (!shift_down && !ed.tool_requires_shift[i])))
            {
                ed.current_tool = (EditorTools) i;
            }
        }

        if (key.keymap[SDLK_w])
        {
            ed.switch_warpdir(shift_down);
            ed.keydelay = 6;
        }
        if (key.keymap[SDLK_e])
        {
            ed.keydelay = 6;
            ed.get_input_line(TEXT_ROOMNAME, "Enter new room name:", const_cast<std::string*>(&(cl.getroomprop(ed.levx, ed.levy)->roomname)));
            game.mapheld = true;
        }
        if (key.keymap[SDLK_g])
        {
            ed.keydelay = 6;
            ed.get_input_line(TEXT_GOTOROOM, "Enter room coordinates x,y:", NULL);
            game.mapheld = true;
        }

        //Save and load
        if (key.keymap[SDLK_s])
        {
            ed.keydelay = 6;
            ed.get_input_line(TEXT_SAVE, "Enter map filename to save as:", &(ed.filename));
            game.mapheld = true;
        }

        if (key.keymap[SDLK_l])
        {
            ed.keydelay = 6;
            ed.get_input_line(TEXT_LOAD, "Enter map filename to load:", &(ed.filename));
            game.mapheld = true;
        }

        ed.f_modifier = key.keymap[SDLK_f];
        ed.h_modifier = key.keymap[SDLK_h];
        ed.v_modifier = key.keymap[SDLK_v];
        ed.b_modifier = key.keymap[SDLK_b];
        ed.c_modifier = key.keymap[SDLK_c];
        ed.x_modifier = key.keymap[SDLK_x];
        ed.z_modifier = key.keymap[SDLK_z];

        const int room = ed.levx + ed.levy * cl.maxwidth;
        const int plat_speed = cl.roomproperties[room].platv;

        if (key.keymap[SDLK_COMMA])
        {
            if (key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL])
            {
                cl.roomproperties[room].platv = plat_speed - 1;
            }
            else
            {
                ed.current_tool = (EditorTools) POS_MOD(ed.current_tool - 1, NUM_EditorTools);
            }
            ed.keydelay = 6;
        }
        else if (key.keymap[SDLK_PERIOD])
        {
            if (key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL])
            {
                cl.roomproperties[room].platv = plat_speed + 1;
            }
            else
            {
                ed.current_tool = (EditorTools) POS_MOD(ed.current_tool + 1, NUM_EditorTools);
            }
            ed.keydelay = 6;
        }

        if (plat_speed != cl.roomproperties[room].platv)
        {
            char buffer[3 * SCREEN_WIDTH_CHARS + 1];
            vformat_buf(
                buffer, sizeof(buffer),
                loc::gettext("Platform speed is now {speed}"),
                "speed:int",
                cl.roomproperties[room].platv
            );
            ed.show_note(buffer);
        }

        if (key.keymap[SDLK_SPACE])
        {
            ed.toolbox_open = !ed.toolbox_open;
            ed.keydelay = 6;
        }
    }
}

void editorclass::get_input_line(const enum TextMode mode, const std::string& prompt, std::string* ptr)
{
    state = EditorState_DRAW;
    substate = EditorSubState_DRAW_INPUT;
    current_text_mode = mode;
    current_text_ptr = ptr;
    current_text_desc = prompt;
    key.enabletextentry();
    if (ptr)
    {
        key.keybuffer = *ptr;
    }
    else
    {
        key.keybuffer = "";
        current_text_ptr = &(key.keybuffer);
    }

    old_entity_text = key.keybuffer;
}

void editorinput(void)
{
    extern editorclass ed;

    if (graphics.fademode == FADE_FADING_OUT)
    {
        return;
    }

    ed.old_tilex = ed.tilex;
    ed.old_tiley = ed.tiley;

    ed.tilex = SDL_clamp(key.mousex, 0, SCREEN_WIDTH_PIXELS - 1) / 8;
    ed.tiley = SDL_clamp(key.mousey, 0, SCREEN_HEIGHT_PIXELS - 1) / 8;

    bool up_pressed = key.isDown(SDLK_UP) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_UP);
    bool down_pressed = key.isDown(SDLK_DOWN) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_DOWN);
    bool left_pressed = key.isDown(SDLK_LEFT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_LEFT);
    bool right_pressed = key.isDown(SDLK_RIGHT) || key.isDown(SDL_CONTROLLER_BUTTON_DPAD_RIGHT);

    game.press_left = false;
    game.press_right = false;
    game.press_action = false;
    game.press_map = false;
    game.press_interact = false;

    if (key.isDown(KEYBOARD_LEFT) || key.isDown(KEYBOARD_a) || key.controllerWantsLeft(false))
    {
        game.press_left = true;
    }
    if (key.isDown(KEYBOARD_RIGHT) || key.isDown(KEYBOARD_d) || key.controllerWantsRight(false))
    {
        game.press_right = true;
    }
    if (key.isDown(KEYBOARD_z) || key.isDown(KEYBOARD_SPACE) || key.isDown(KEYBOARD_v) || key.isDown(game.controllerButton_flip))
    {
        game.press_action = true;
    };

    if (key.keymap[SDLK_F9] && (ed.keydelay == 0)) {
        ed.keydelay = 30;
        ed.show_note(loc::gettext("Reloaded resources"));
        graphics.reloadresources();
    }

    // Was escape just pressed?
    bool escape_pressed = false;
    if (key.isDown(27) && !ed.settingskey)
    {
        ed.settingskey = true;
        escape_pressed = true;
    }
    else if (!key.isDown(27))
    {
        ed.settingskey = false;
    }

    // What about enter?
    bool enter_pressed = false;
    if (key.isDown(KEYBOARD_ENTER) && !game.mapheld)
    {
        game.mapheld = true;
        enter_pressed = true;
    }
    else if (!key.isDown(KEYBOARD_ENTER))
    {
        game.mapheld = false;
    }

    bool shift_down = key.keymap[SDLK_LSHIFT] || key.keymap[SDLK_RSHIFT];
    bool ctrl_down = key.keymap[SDLK_LCTRL] || key.keymap[SDLK_RCTRL];

    // Do different things depending on the current state (and substate)
    switch (ed.state)
    {
        // Draw mode, aka placing tiles and entities down
    case EditorState_DRAW:
        switch (ed.substate)
        {
        case EditorSubState_MAIN:
            if (escape_pressed)
            {
                // We're just in draw mode, so go to the settings menu
                music.playef(Sound_VIRIDIAN);
                ed.state = EditorState_MENU;
                ed.substate = EditorSubState_MAIN;
                game.createmenu(Menu::ed_settings);
            }

            if (ctrl_down)
            {
                // Holding ctrl, show the direct mode tile drawer
                ed.direct_mode_drawer = 10;
            }

            if (ed.keydelay > 0)
            {
                ed.keydelay--;
            }
            else if (up_pressed || down_pressed || left_pressed || right_pressed)
            {
                ed.keydelay = 6;

                if (ctrl_down)
                {
                    // The direct mode drawer is open, so arrow keys should change what tile you have selected
                    // Also let's make it a little faster
                    ed.keydelay = 3;

                    int texturewidth;
                    int textureheight;

                    bool tiles1 = (cl.getroomprop(ed.levx, ed.levy)->tileset == 0);

                    if (graphics.query_texture(tiles1 ? graphics.grphx.im_tiles : graphics.grphx.im_tiles2, NULL, NULL, &texturewidth, &textureheight) != 0)
                        return;

                    const int numtiles = (int)(texturewidth / 8) * (textureheight / 8);

                    if (left_pressed) ed.direct_mode_tile--;
                    if (right_pressed) ed.direct_mode_tile++;
                    if (up_pressed) ed.direct_mode_tile -= 40;
                    if (down_pressed) ed.direct_mode_tile += 40;

                    ed.direct_mode_tile = POS_MOD(ed.direct_mode_tile, numtiles);
                }
                else if (shift_down)
                {

                    if (up_pressed) cl.mapheight--;
                    if (down_pressed) cl.mapheight++;
                    if (left_pressed) cl.mapwidth--;
                    if (right_pressed) cl.mapwidth++;

                    cl.mapwidth = SDL_clamp(cl.mapwidth, 1, cl.maxwidth);
                    cl.mapheight = SDL_clamp(cl.mapheight, 1, cl.maxheight);

                    ed.updatetiles = true;
                    ed.changeroom = true;
                    graphics.backgrounddrawn = false;
                    graphics.foregrounddrawn = false;

                    ed.levx = POS_MOD(ed.levx, cl.mapwidth);
                    ed.levy = POS_MOD(ed.levy, cl.mapheight);

                    char buffer[3 * SCREEN_WIDTH_CHARS + 1];
                    vformat_buf(
                        buffer, sizeof(buffer),
                        loc::gettext("Mapsize is now [{width},{height}]"),
                        "width:int, height:int",
                        cl.mapwidth, cl.mapheight
                    );

                    ed.show_note(buffer);
                }
                else
                {
                    ed.updatetiles = true;
                    ed.changeroom = true;
                    graphics.backgrounddrawn = false;
                    graphics.foregrounddrawn = false;

                    if (up_pressed) ed.levy--;
                    if (down_pressed) ed.levy++;
                    if (left_pressed) ed.levx--;
                    if (right_pressed) ed.levx++;

                    ed.levx = POS_MOD(ed.levx, cl.mapwidth);
                    ed.levy = POS_MOD(ed.levy, cl.mapheight);

                }
            }
            else
            {
                handle_draw_input();
            }

            // Mouse input
            if (key.leftbutton && ed.lclickdelay == 0)
            {
                ed.tool_place();
            }
            else if (!key.leftbutton)
            {
                ed.lclickdelay = 0;
            }

            if (key.rightbutton)
            {
                ed.tool_remove();
            }

            if (key.middlebutton)
            {
                ed.direct_mode_tile = cl.gettile(ed.levx, ed.levy, ed.tilex, ed.tiley);
            }

            if (enter_pressed)
            {
                start_at_checkpoint();
            }

            break;

        case EditorSubState_DRAW_BOX:
            if (escape_pressed)
            {
                // Cancel box placement
                ed.substate = EditorSubState_MAIN;
            }

            if (key.leftbutton && ed.lclickdelay == 0)
            {
                if (ed.box_corner == BoxCorner_FIRST)
                {
                    ed.lclickdelay = 1;
                    ed.box_point.x = ed.tilex * 8;
                    ed.box_point.y = ed.tiley * 8;
                    ed.box_corner = BoxCorner_LAST;
                }
                else if (ed.box_corner == BoxCorner_LAST)
                {
                    int left;
                    int right;
                    int top;
                    int bottom;

                    adjust_box_coordinates(ed.box_point.x, ed.box_point.y, ed.tilex * 8, ed.tiley * 8, &left, &right, &top, &bottom);

                    ed.lclickdelay = 1;
                    ed.substate = EditorSubState_MAIN;

                    switch (ed.box_type)
                    {
                    case BoxType_SCRIPT:
                        ed.text_entity = customentities.size();

                        ed.add_entity(ed.levx, ed.levy, left / 8, top / 8, 19, (right - left) / 8, (bottom - top) / 8);

                        ed.get_input_line(TEXT_SCRIPT, "Enter script name:", &(customentities[ed.text_entity].scriptname));
                        break;
                    case BoxType_ENEMY:
                        cl.setroomenemyx1(ed.levx, ed.levy, left);
                        cl.setroomenemyy1(ed.levx, ed.levy, top);
                        cl.setroomenemyx2(ed.levx, ed.levy, right);
                        cl.setroomenemyy2(ed.levx, ed.levy, bottom);
                        break;
                    case BoxType_PLATFORM:
                        cl.setroomplatx1(ed.levx, ed.levy, left);
                        cl.setroomplaty1(ed.levx, ed.levy, top);
                        cl.setroomplatx2(ed.levx, ed.levy, right);
                        cl.setroomplaty2(ed.levx, ed.levy, bottom);
                        break;
                    case BoxType_COPY:
                        // Unused
                        break;
                    }
                }
            }
            else if (!key.leftbutton)
            {
                ed.lclickdelay = 0;
            }

            if (key.rightbutton)
            {
                ed.substate = EditorSubState_MAIN;
            }

            break;

        case EditorSubState_DRAW_WARPTOKEN:
            if (escape_pressed || key.rightbutton)
            {
                // Cancel warp token placement
                ed.substate = EditorSubState_MAIN;
                ed.remove_entity(ed.warp_token_entity);
                ed.warp_token_entity = -1;
            }

            // Allow the user to change rooms while placing a warp token
            if (ed.keydelay > 0)
            {
                ed.keydelay--;
            }
            else if (up_pressed || down_pressed || left_pressed || right_pressed)
            {
                ed.keydelay = 6;

                ed.updatetiles = true;
                ed.changeroom = true;
                graphics.backgrounddrawn = false;
                graphics.foregrounddrawn = false;

                if (up_pressed) ed.levy--;
                if (down_pressed) ed.levy++;
                if (left_pressed) ed.levx--;
                if (right_pressed) ed.levx++;

                ed.levx = POS_MOD(ed.levx, cl.mapwidth);
                ed.levy = POS_MOD(ed.levy, cl.mapheight);
            }

            // Left click means place!
            if (key.leftbutton)
            {
                if (ed.lclickdelay == 0)
                {
                    customentities[ed.warp_token_entity].p1 = ed.tilex + (ed.levx * 40);
                    customentities[ed.warp_token_entity].p2 = ed.tiley + (ed.levy * 30);

                    ed.substate = EditorSubState_MAIN;

                    ed.warp_token_entity = -1;
                    ed.lclickdelay = 1;
                }
            }
            else
            {
                ed.lclickdelay = 0;
            }

            break;

        case EditorSubState_DRAW_INPUT:
            // We're taking input!
            if (ed.current_text_mode == TEXT_SCRIPT)
            {
                // This is editing an entity's text field, currently only used as a script name.
                // Remove all pipes, they are the line separator in the XML.
                // When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0
                for (size_t i = key.keybuffer.length() - 1; i + 1 > 0; i--)
                {
                    if (key.keybuffer[i] == '|')
                    {
                        key.keybuffer.erase(key.keybuffer.begin() + i);
                    }
                }
            }

            if (escape_pressed)
            {
                // Cancel it, and remove the enemy it's tied to if necessary
                key.disabletextentry();
                if (ed.current_text_mode >= FIRST_ENTTEXT && ed.current_text_mode <= LAST_ENTTEXT)
                {
                    *ed.current_text_ptr = ed.old_entity_text;
                    if (ed.old_entity_text == "")
                    {
                        ed.remove_entity(ed.text_entity);
                    }
                }

                ed.current_text_mode = TEXT_NONE;

                ed.help_open = false;

                ed.substate = EditorSubState_MAIN;
            }

            if (enter_pressed)
            {
                input_submitted();
            }
            break;
        default:
            break;
        }
        break;

    // We're in the menu!
    case EditorState_MENU:
        up_pressed |= key.isDown(KEYBOARD_w);
        down_pressed |= key.isDown(KEYBOARD_s);

        switch (ed.substate)
        {
        case EditorSubState_MAIN:
            if (!game.press_action && !game.press_left && !game.press_right && !up_pressed && !down_pressed)
            {
                game.jumpheld = false;
            }

            if (!game.jumpheld)
            {
                if (game.press_action || game.press_left || game.press_right || game.press_map || up_pressed || down_pressed)
                {
                    game.jumpheld = true;
                }

                if (game.menustart)
                {
                    bool left, right;
                    if (!font::is_rtl(PR_FONT_INTERFACE))
                    {
                        left = game.press_left;
                        right = game.press_right;
                    }
                    else
                    {
                        left = game.press_right;
                        right = game.press_left;
                    }

                    if (left || up_pressed)
                    {
                        game.currentmenuoption--;
                    }
                    else if (right || down_pressed)
                    {
                        game.currentmenuoption++;
                    }
                }

                game.currentmenuoption = POS_MOD(game.currentmenuoption, (int)game.menuoptions.size());

                if (game.press_action)
                {
                    editormenuactionpress();
                }
            }

            // Was escape pressed?
            if (escape_pressed)
            {
                bool esc_from_font = false;
                music.playef(Sound_VIRIDIAN);

                if (game.currentmenuname == Menu::ed_settings)
                {
                    ed.state = EditorState_DRAW;
                }
                else
                {
                    // Avoid double return
                    esc_from_font = game.currentmenuname == Menu::ed_font;

                    game.returnmenu();
                    map.nexttowercolour();
                }

                if (ed.state == EditorState_MENU && !esc_from_font)
                {
                    bool edsettings_in_stack = game.currentmenuname == Menu::ed_settings;
                    if (!edsettings_in_stack)
                    {
                        size_t i;
                        for (i = 0; i < game.menustack.size(); ++i)
                        {
                            if (game.menustack[i].name == Menu::ed_settings)
                            {
                                edsettings_in_stack = true;
                                break;
                            }
                        }
                    }
                    if (edsettings_in_stack)
                    {
                        game.returntomenu(Menu::ed_settings);
                    }
                    else
                    {
                        game.createmenu(Menu::ed_settings);
                    }
                    map.nexttowercolour();
                }
            }
            break;

        case EditorSubState_MENU_INPUT:
            if (escape_pressed && key.textentry())
            {
                ed.substate = EditorSubState_MAIN;
                key.disabletextentry();
                ed.current_text_mode = TEXT_NONE;

                music.playef(Sound_VIRIDIAN);
            }

            if (enter_pressed)
            {
                input_submitted();
            }
            break;
        default:
            break;
        }
        break;

    case EditorState_SCRIPTS:
    {
        switch (ed.substate)
        {
        case EditorSubState_MAIN:
        {
            up_pressed |= key.isDown(KEYBOARD_w);
            down_pressed |= key.isDown(KEYBOARD_s);

            if (escape_pressed)
            {
                music.playef(Sound_VIRIDIAN);
                ed.state = EditorState_MENU;
                ed.substate = EditorSubState_MAIN;
            }

            if (ed.keydelay > 0) ed.keydelay--;

            if (up_pressed && ed.keydelay <= 0)
            {
                ed.keydelay = 3;
                ed.selected_script--;
            }

            if (down_pressed && ed.keydelay <= 0)
            {
                ed.keydelay = 3;
                ed.selected_script++;
            }

            ed.selected_script = SDL_clamp(ed.selected_script, 0, (int) script.customscripts.size() - 1);

            if (ed.selected_script < ed.script_list_offset)
            {
                ed.script_list_offset = ed.selected_script;
            }

            if (ed.selected_script >= ed.script_list_offset + 9)
            {
                ed.script_list_offset = ed.selected_script - 8;
            }

            if (!key.keymap[SDLK_BACKSPACE])
            {
                ed.backspace_held = false;
            }

            if (key.keymap[SDLK_BACKSPACE] && !ed.backspace_held && !script.customscripts.empty())
            {
                ed.backspace_held = true;
                music.playef(Sound_CRY);
                ed.remove_script(script.customscripts[(script.customscripts.size() - 1) - ed.selected_script].name);
            }

            if (!game.press_action && !game.press_left && !game.press_right && !up_pressed && !down_pressed && !key.isDown(27))
            {
                game.jumpheld = false;
            }

            if (!game.jumpheld)
            {
                if (game.press_action || game.press_left || game.press_right || game.press_map || up_pressed || down_pressed || key.isDown(27))
                {
                    game.jumpheld = true;
                }

                if ((game.press_action || game.press_map) && !script.customscripts.empty())
                {
                    game.mapheld = true;
                    ed.substate = EditorSubState_SCRIPTS_EDIT;
                    key.enabletextentry();
                    key.keybuffer = "";
                    ed.current_text_ptr = &(key.keybuffer);
                    ed.current_script = script.customscripts[(script.customscripts.size() - 1) - ed.selected_script].name;
                    ed.load_script_in_editor(ed.current_script);

                    ed.script_cursor_y = ed.script_buffer.size() - 1;
                    ed.script_offset = SDL_max(ed.script_cursor_y - (ed.lines_visible - SCRIPT_LINE_PADDING), 0);

                    key.keybuffer = ed.script_buffer[ed.script_cursor_y];
                    ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str());

                    music.playef(Sound_VIRIDIAN);
                }
            }
            break;
        }
        case EditorSubState_SCRIPTS_EDIT:
        {
            // Script editor!
            if (escape_pressed)
            {
                music.playef(Sound_VIRIDIAN);
                ed.substate = EditorSubState_MAIN;

                // Alright, now re-add the script.
                ed.create_script(ed.current_script, ed.script_buffer);

                key.disabletextentry();
            }

            if (ed.keydelay > 0) ed.keydelay--;

            if (up_pressed && ed.keydelay <= 0)
            {
                ed.keydelay = 3;
                ed.script_cursor_y = SDL_max(0, ed.script_cursor_y - 1);

                key.keybuffer = ed.script_buffer[ed.script_cursor_y];
            }

            if (down_pressed && ed.keydelay <= 0)
            {
                ed.keydelay = 3;
                ed.script_cursor_y = SDL_min((int) ed.script_buffer.size() - 1, ed.script_cursor_y + 1);

                key.keybuffer = ed.script_buffer[ed.script_cursor_y];
            }

            if (key.linealreadyemptykludge)
            {
                ed.keydelay = 6;
                key.linealreadyemptykludge = false;
            }

            if (key.pressedbackspace && ed.script_buffer[ed.script_cursor_y] == "" && ed.keydelay <= 0)
            {
                //Remove this line completely
                ed.remove_line(ed.script_cursor_y);
                ed.script_cursor_y = SDL_max(0, ed.script_cursor_y - 1);
                key.keybuffer = ed.script_buffer[ed.script_cursor_y];
                ed.keydelay = 6;
            }

            /* Remove all pipes, they are the line separator in the XML
             * When this loop reaches the end, it wraps to SIZE_MAX; SIZE_MAX + 1 is 0 */
            {size_t i; for (i = key.keybuffer.length() - 1; i + 1 > 0; --i)
            {
                if (key.keybuffer[i] == '|')
                {
                    key.keybuffer.erase(key.keybuffer.begin() + i);
                }
            }}

            ed.script_buffer[ed.script_cursor_y] = key.keybuffer;
            ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str());

            if (enter_pressed)
            {
                //Continue to next line
                if (ed.script_cursor_y >= (int)ed.script_buffer.size()) //we're on the last line
                {
                    ed.script_cursor_y++;

                    key.keybuffer = ed.script_buffer[ed.script_cursor_y];
                    ed.script_cursor_x = UTF8_total_codepoints(ed.script_buffer[ed.script_cursor_y].c_str());
                }
                else
                {
                    //We're not, insert a line instead
                    ed.script_cursor_y++;

                    ed.insert_line(ed.script_cursor_y);
                    key.keybuffer = "";
                    ed.script_cursor_x = 0;
                }
            }

            if (ed.script_cursor_y < ed.script_offset + SCRIPT_LINE_PADDING)
            {
                ed.script_offset = SDL_max(0, ed.script_cursor_y - SCRIPT_LINE_PADDING);
            }

            if (ed.script_cursor_y > ed.script_offset + ed.lines_visible - SCRIPT_LINE_PADDING)
            {
                ed.script_offset = SDL_min((int) ed.script_buffer.size() - ed.lines_visible + SCRIPT_LINE_PADDING, ed.script_cursor_y - ed.lines_visible + SCRIPT_LINE_PADDING);
            }

            break;
        }
        default:
            break;
        }
        break;
    }
    }

    if (ed.updatetiles && cl.getroomprop(ed.levx, ed.levy)->directmode == 0)
    {
        for (int i = 0; i < SCREEN_WIDTH_TILES * SCREEN_HEIGHT_TILES; i++)
        {
            int tile_x = i % SCREEN_WIDTH_TILES;
            int tile_y = i / SCREEN_WIDTH_TILES;
            ed.set_tile(tile_x, tile_y, ed.autotile(tile_x, tile_y));
        }

        ed.updatetiles = false;
        graphics.foregrounddrawn = false;
    }
}

bool editorclass::is_warp_zone_background(int tile)
{
    if (cl.getroomprop(levx, levy)->tileset == EditorTileset_SPACE_STATION)
    {
        return false;
    }

    return (tile == 120 || tile == 123 || tile == 126 || tile == 129 || tile == 132 || tile == 135 || tile == 138);
}

int editorclass::autotile(const int x, const int y)
{
    int tile = get_tile(x, y);
    TileTypes type = get_tile_type(x, y, false);

    if (tile == 0)
    {
        return 0;
    }

    if (type == TileType_SPIKE)
    {
        bool tile_up = get_tile_type(x, y - 1, false) == TileType_SOLID;
        bool tile_down = get_tile_type(x, y + 1, false) == TileType_SOLID;
        bool tile_left = get_tile_type(x - 1, y, false) == TileType_SOLID;
        bool tile_right = get_tile_type(x + 1, y, false) == TileType_SOLID;

        if (cl.getroomprop(levx, levy)->tileset == EditorTileset_LAB)
        {
            // If this is the lab, use the colourful lab spikes!
            int mult = cl.getroomprop(levx, levy)->tilecol;
            if (tile_down)
                return 63 + mult * 2;
            if (tile_up)
                return 64 + mult * 2;
            if (tile_left)
                return 51 + mult * 2;
            if (tile_right)
                return 52 + mult * 2;
            return 63 + mult * 2;
        }

        // Not in the lab, so use the boring normal spikes
        if (tile_down)
            return 8;
        if (tile_up)
            return 9;
        if (tile_left)
            return 49;
        if (tile_right)
            return 50;
        return 8;
    }

    bool tile_up = autotile_connector(x, y - 1, type);
    bool tile_down = autotile_connector(x, y + 1, type);
    bool tile_left = autotile_connector(x - 1, y, type);
    bool tile_right = autotile_connector(x + 1, y, type);

    bool tile_up_left = autotile_connector(x - 1, y - 1, type);
    bool tile_up_right = autotile_connector(x + 1, y - 1, type);
    bool tile_down_left = autotile_connector(x - 1, y + 1, type);
    bool tile_down_right = autotile_connector(x + 1, y + 1, type);

    int tile_value = 0;

    if (tile_up)
        tile_value += 1;
    if (tile_up_right)
        tile_value += 2;
    if (tile_right)
        tile_value += 4;
    if (tile_down_right)
        tile_value += 8;
    if (tile_down)
        tile_value += 16;
    if (tile_down_left)
        tile_value += 32;
    if (tile_left)
        tile_value += 64;
    if (tile_up_left)
        tile_value += 128;

    bool background = (type == TileType_NONSOLID || is_warp_zone_background(tile));
    EditorTilecolInfo data = get_tilecol_data();

    int base = background ? data.background_base : data.foreground_base;
    return base + autotile_types[background ? data.background_type : data.foreground_type][tile_value];
}

EditorTilecolInfo editorclass::get_tilecol_data(void)
{
    EditorTilesets tileset = (EditorTilesets) cl.getroomprop(levx, levy)->tileset;
    int tilecol = cl.getroomprop(levx, levy)->tilecol;

    return tileset_colors[tileset][tilecol];
}

bool editorclass::autotile_connector(int x, int y, TileTypes original_type)
{
    if (x < 0 || x >= SCREEN_WIDTH_TILES || y < 0 || y >= SCREEN_HEIGHT_TILES)
    {
        return true;
    }

    int tile = get_tile(x, y);
    TileTypes new_type = get_tile_type(x, y, false);

    if (tile == 0)
    {
        return false;
    }

    EditorTilecolInfo data = get_tilecol_data();

    if (original_type == TileType_NONSOLID)
    {
        if (data.bg_ignores_walls)
        {
            return new_type == TileType_NONSOLID || is_warp_zone_background(tile);
        }
        return true;
    }

    if (new_type == TileType_SOLID && !is_warp_zone_background(tile))
    {
        return true;
    }

    return false;
}

int editorclass::get_enemy_tile(int t)
{
    switch(t)
    {
    case 0:
        return 78;
        break;
    case 1:
        return 88;
        break;
    case 2:
        return 36;
        break;
    case 3:
        return 164;
        break;
    case 4:
        return 68;
        break;
    case 5:
        return 48;
        break;
    case 6:
        return 176;
        break;
    case 7:
        return 168;
        break;
    case 8:
        return 112;
        break;
    case 9:
        return 114;
        break;
    default:
        return 78;
        break;
    }
}

void editorclass::set_tile(int x, int y, int t)
{
    if (x >= 0 && y >= 0 && x < SCREEN_WIDTH_TILES && y < SCREEN_HEIGHT_TILES)
    {
        cl.settile(levx, levy, x, y, t);
    }
    graphics.foregrounddrawn = false;
    updatetiles = true;
}

int editorclass::get_tile(const int x, const int y)
{
    if (x >= 0 && y >= 0 && x < SCREEN_WIDTH_TILES && y < SCREEN_HEIGHT_TILES)
    {
        return cl.gettile(levx, levy, x, y);
    }

    return 0;
}

TileTypes editorclass::get_abs_tile_type(int x, int y, const bool wrap)
{
    if (wrap)
    {
        x = POS_MOD(x, cl.mapwidth * 40);
        y = POS_MOD(y, cl.mapheight * 30);
    }
    else
    {
        x = SDL_clamp(x, 0, cl.mapwidth * 40 - 1);
        y = SDL_clamp(y, 0, cl.mapheight * 30 - 1);
    }

    const RoomProperty* const room = cl.getroomprop(x / 40, y / 30);
    int tile = cl.getabstile(x, y);

    if (tile == 1 || (tile >= 80 && tile <= 679))
    {
        // It's solid.
        return TileType_SOLID;
    }

    if ((tile >= 6 && tile <= 9) || tile == 49 || tile == 50)
    {
        // It's a spike!
        return TileType_SPIKE;
    }

    if (room->tileset != 0)
    {
        // tiles2.png is slightly different.

        if (tile >= 51 && tile <= 74)
        {
            // It has more spikes!
            return TileType_SPIKE;
        }

        if (tile == 740)
        {
            // And a stray solid.
            return TileType_SOLID;
        }
    }

    return TileType_NONSOLID;
}

TileTypes editorclass::get_tile_type(int x, int y, bool wrap)
{
    if (wrap)
    {
        x = POS_MOD(x, 40);
        y = POS_MOD(y, 30);
    }
    else
    {
        x = SDL_clamp(x, 0, 39);
        y = SDL_clamp(y, 0, 29);
    }

    return get_abs_tile_type(levx * 40 + x, levy * 30 + y, false);
}

bool editorclass::lines_can_pass(int x, int y)
{
    const int tile = cl.gettile(levx, levy, x, y);
    if (x >= 0 && y >= 0 && x < SCREEN_WIDTH_TILES && y < SCREEN_HEIGHT_TILES)
    {
        return tile == 0 || tile >= 680;
    }
    if (x == -1 || y == -1 || x == SCREEN_WIDTH_TILES || y == SCREEN_HEIGHT_TILES)
    {
        // If lines go offscreen, they stick out one tile
        return true;
    }
    return false;
}

void editorclass::make_autotiling_base(void)
{
    if (cl.getroomprop(levx, levy)->directmode == 1)
    {
        return;
    }

    for (int i = 0; i < SCREEN_WIDTH_TILES * SCREEN_HEIGHT_TILES; i++)
    {
        int tile_x = i % SCREEN_WIDTH_TILES;
        int tile_y = i / SCREEN_WIDTH_TILES;
        int tile = get_tile(tile_x, tile_y);

        if (tile == 0)
        {
            continue;
        }

        TileTypes type = get_tile_type(tile_x, tile_y, false);

        switch (type)
        {
        case TileType_NONSOLID:
            set_tile(tile_x, tile_y, 2);
            break;
        case TileType_SOLID:
            if (is_warp_zone_background(tile))
            {
                set_tile(tile_x, tile_y, 2);
            }
            else
            {
                set_tile(tile_x, tile_y, 1);
            }
            break;
        case TileType_SPIKE:
            set_tile(tile_x, tile_y, 6);
            break;
        }
    }
}

void editorclass::switch_tileset(const bool reversed)
{
    make_autotiling_base();

    int tiles = cl.getroomprop(levx, levy)->tileset;

    if (reversed)
    {
        tiles--;
    }
    else
    {
        tiles++;
    }

    tiles = POS_MOD(tiles, NUM_EditorTilesets);
    cl.setroomtileset(levx, levy, tiles);

    clamp_tilecol(levx, levy, false);

    char buffer[3*SCREEN_WIDTH_CHARS + 1];
    vformat_buf(
        buffer, sizeof(buffer),
        loc::gettext("Now using {area} Tileset"),
        "area:str",
        loc::gettext(tileset_names[tiles])
    );

    show_note(buffer);

    updatetiles = true;

    graphics.backgrounddrawn = false;
}

void editorclass::switch_tilecol(const bool reversed)
{
    make_autotiling_base();

    int tilecol = cl.getroomprop(levx, levy)->tilecol;

    if (reversed)
    {
        tilecol--;
    }
    else
    {
        tilecol++;
    }

    cl.setroomtilecol(levx, levy, tilecol);

    clamp_tilecol(levx, levy, true);

    show_note(loc::gettext("Tileset Colour Changed"));

    updatetiles = true;

    graphics.backgrounddrawn = false;
}

void editorclass::clamp_tilecol(const int rx, const int ry, const bool wrap)
{
    const RoomProperty* const room = cl.getroomprop(rx, ry);
    const int tileset = room->tileset;
    int tilecol = room->tilecol;

    int mincol = (room->directmode ? tileset_min_colour_direct : tileset_min_colour)[tileset];
    int maxcol = (room->directmode ? tileset_max_colour_direct : tileset_max_colour)[tileset];

    // If wrap is true, wrap-around, otherwise just cap
    if (tilecol > maxcol)
    {
        tilecol = (wrap ? mincol : maxcol);
    }
    if (tilecol < mincol)
    {
        tilecol = (wrap ? maxcol : mincol);
    }

    cl.setroomtilecol(rx, ry, tilecol);
}

void editorclass::switch_enemy(const bool reversed)
{
    const RoomProperty* const room = cl.getroomprop(levx, levy);

    int enemy = room->enemytype;

    if (reversed)
    {
        enemy--;
    }
    else
    {
        enemy++;
    }

    const int modulus = 10;
    enemy = POS_MOD(enemy, modulus);
    cl.setroomenemytype(levx, levy, enemy);

    show_note(loc::gettext("Enemy Type Changed"));
}

void editorclass::switch_warpdir(const bool reversed)
{
    static const int modulus = 4;
    const RoomProperty* const room = cl.getroomprop(levx, levy);

    int warpdir = room->warpdir;

    if (reversed)
    {
        --warpdir;
    }
    else
    {
        ++warpdir;
    }

    warpdir = POS_MOD(warpdir, modulus);
    cl.setroomwarpdir(levx, levy, warpdir);

    switch (warpdir)
    {
    default:
        show_note(loc::gettext("Room warping disabled"));
        break;
    case 1:
        show_note(loc::gettext("Room warps horizontally"));
        break;
    case 2:
        show_note(loc::gettext("Room warps vertically"));
        break;
    case 3:
        show_note(loc::gettext("Room warps in all directions"));
        break;
    }

    graphics.backgrounddrawn = false;
}
