Graal Pastebin

New Paste
Pasted on September 17, 2012.
View the raw file

/**************************************************************\
#Title
Pixel Perfect Tile Editor
 
#Version
1.3
 
#Features
- Relatively easy setup with simple configuration
- Works with graal file browser rights system
- Scrolls automatically when selecting large portions of the gui
- Open, save, and delete tile clipboard files
- Undo & redo (Note: history is cleared when the editor closes)
- [Toggle] Fade inactive tilelayers
- [Toggle] Level grid to separate tiles
- [Toggle] Extra cursor info
- [Toggle] External window
- Create new levels
- Flood fill
- Can check for updates
- More
 
#Installation
1. Create a new weapon script named -Development/TileEditor or
   whatever whatever is pleasing to you
2. Copy the entire text of this document into the script
3. Modify the configuration portion of the script to suit your
   server best
4. Give (npcserver) the required rights
   By default, this includes:
     rw access to TileEditor/Clipboard/ and subfolders,
     rw access to TileEditor/Data/ and subfolders,
     rw access to a levels folder (to create new levels),
     rw access to levels/images/tileeditor_icon_1-1.png ,
     rw access to levels/images/tileeditor_bucket_1-1.png ,
     rw access to levels/images/tileeditor_pencil_1-1.png
5. Give level designers necessary rights
     r and/or w access to TileEditor/Clipboard/ and/or 
     subfolders,
     rw access to level files to use this program on
6. Add the weapon script to the level designers
 
#Script Interface
- The following clientside public functions are available:
  open() - triggers the request to open the editor
  close() - triggers the program to close
  isOpen() - returns true if the program is open
 
#Directions
Type /te into the chat bar to open the program
Left click to select tiles, right click to paste the selection
 
#Credits
Scripted by Pixel
\**************************************************************/
 
/*********************** Configuration ************************/
// Check for updates when the weapon script is initialized?
// (recommended to know when future updates are released)
const TE_CHECK_UPDATE = true;
 
// Must the user be in the serveroptions staff list?
// Set the value to true if you want to restrict the use of the
// program to only staff. Otherwise set the value to false.
const TE_ONLYSTAFF = true;
 
// Must the user have rights to levels and files?
// Set the value to true if you want to secure levels and files
// using the default graal rights system (Strongly recommended).
// Otherwise set the value to false
const TE_NEEDRIGHTS = true;
 
// Where should the clipboard files be stored?
// (recommended to keep this as "TileEditor/Clipboard/")
const TE_DIR_CLIPBOARD = "TileEditor/Clipboard/";
 
// Where should the program keep it's data files?
// (recommended to keep this as "TileEditor/Data/")
const TE_DIR_DATAFILES = "TileEditor/Data/";
 
// Should the program save images and icons to the server?
// (recommended to keep this as true usually)
const TE_WRITE_IMAGES = true;
 
// Where should the program save it's images and icons?
// This can vary depending on your server's folder options
const TE_DIR_IMAGES = "levels/images/";
/**************************************************************/
 
/******************* Don't Touch Below Here *******************/
 
const TE_VERSION = "1.3.0";
 
// images are stored here as base 64 encoded strings. the images
// are saved to the server onCreated
const TE_ICON_DATA = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAB3RJTUUH3AYDFREoSFzGywAAABd0RVh0U29mdHdhcmUAR0xEUE5HIHZlciAzLjRxhaThAAAACHRwTkdHTEQzAAAAAEqAKR8AAAAEZ0FNQQAAsY8L/GEFAAADAFBMVEUAAACAAAAAgACAgADAwMCAAIAAgIAAAIDA3MCmyvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+/CgoKSAgID/AAAA/wD//wAAAP//AP8A//////+6YMT6AAAAAWJLR0T/pQfyxQAAAHBJREFUeJxljjEOgDAMA93AENGJ/z8SMQVaJMghsdCTslh2bE29T0fEYa1ZuId6EklLPBmFf8TmUuZ7Xe+SLPu+6EqwEj2TQbAq1U3aXHJO76N8yGMKhJ1KqomNwj9idLOBLWwSVViJMmEQ7OtnC5sexROSefnyghYAAAAASUVORK5CYII=";
const TE_BUCKET_DATA = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAALISURBVDiNjZNLTFRnFMd/3537fXfuTUZa7IiBMDFprInxURIJ7Uqxm8b4SMowjPiANrG8FHXZaGVRVColJiAkoqs2jQbpgoUFN+7wEcXuuqNJIeMAGkoodx7eufdzYcYQY7UnOavzO/9z8j85QmvNuyKRjFcAvUAtkAXuAOdGbo4+AzDe2f0qLgEZYCewF/gA6C4Wzf8h0AhUSSmv+r4/HgRBG/AcaHnvBolkfA1AyZqS+x3tJ3batt0F2ECoyLxVIJGMGw0H648ppWYArgwMhbdu2SpajrWGpVRDq1nzYMe1DVKZwwXP3wUEgfePDjsfqprqKvF1U7No/uYoqVSK5eVltm3bbsRilV9OT0+/FjCUkn2bNm344vPaHdI2/rIqSubDvRe7jfbWNjEz8zdCGPT8eIH0XJpUKkXn8ZNhwEsk4yaA8eKFd6C6Zotxb2xI7/5ss+45fxHbtvF9n77LP7Hx441UVsYYn7iN67qEwzb79u4PQqFQZ9GDkGUptP2JfvLHVE5rzeLiIq7rYlkWpjSJRCKEQiZSSmZnZ6n7Km45jvNDIhkve22iMEtEOj039vjxIz8SieC6Lp0nTrHirjC/ME/TkSaUUhQHtHzbFlZKDa6+gij4nB6+fjXnOA6e5xH9KMqlnl7OfHeW8vIKlFJIKcnn81R9WmUEQbDHEEIUVv7NAPgjN27M5XK5c6O/3cqXlpbiui75fB6lFEopLMsiFotRVraOwaGBnBBizJDSHPn1l9u+Kc1RgCAI+icmxucy2QxCCDKZDFJKHMchGo0y9WTKb+toXXnw8MFpz/MOobUm2T5crrWmmPUNdbu/7zqTyWazemFhQfu+r5+mn+rOU8ezR5oO/VzfULe2yLK6cXU2Hk7+PnlvMlhaWtJXBvtzR5sP/1nfUFf9Jif+650Tyfh6acq7CEoLhUKX1vrayM3R4E3uJf0eQEfPmPQuAAAAAElFTkSuQmCC";
const TE_PENCIL_DATA = "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";
 
// enumeration of network replies
// this list must match the clientside list exactly. otherwise
// bugs will occur
enum RPL {
  NONE,
  ERROR,
  REQUESTUSE,
  PLACETILES,
  LISTCLIPBOARDFOLDER,
  LOADCLIPBOARDFILE,
  SAVECLIPBOARDFILE,
  DELETECLIPBOARDFILE,
  CREATENEWLEVEL
}
 
/**
 * events
 */
 
function onCreated() {
  // if configured to check for updates at initialization
  // do that
  if (TE_CHECK_UPDATE) {
    temp.req = requesturl("http://forums.graalonline.com/forums/showthread.php?t=134267103");
    temp.time = timevar2;
    waitfor(temp.req, "onReceiveData", 10);
    if (timevar2 - temp.time < 10) {
      temp.position = temp.req.fulldata.pos("[VERSION]") + 9;
      temp.currentVersionString = temp.req.fulldata.substring(temp.position, temp.req.fulldata.pos("[/VERSION]") - position);
      if (TE_VERSION != temp.currentVersionString)
        TE_echo("A new version is available: " @ temp.currentVersionString);
    }
  }
  // if the image saving is disabled in the config
  // there's no need to continue
  if (!TE_WRITE_IMAGES)
    return;
 
  // check if the icon image is on the server
  // if it is not, attempt to write it
  temp.filePath = TE_DIR_IMAGES @ "tileeditor_icon_1-1.png";
  temp.files.loadfolder(temp.filePath, 0);
  if (temp.files.size() == 0) {
    temp.data = base64decode(TE_ICON_DATA);
    temp.data.savestring(temp.filePath, 0);
    temp.files = NULL;
    temp.files.loadfolder(temp.filePath, 0);
    if (temp.files.size() == 0) {
      TE_echo("prob: (npcserver) cannot write the icon image file as" SPC temp.filePath @ ". Ensure that both the configuration and the (npcserver)'s rights are correct");
    } else {
      TE_echo("icon image saved to" SPC temp.filePath);
    }
  }
 
  // check if the bucket image is on the server
  // if it is not, attempt to write it
  temp.filePath = TE_DIR_IMAGES @ "tileeditor_bucket_1-1.png";
  temp.files.loadfolder(temp.filePath, 0);
  if (temp.files.size() == 0) {
    temp.data = base64decode(TE_BUCKET_DATA);
    temp.data.savestring(temp.filePath, 0);
    temp.files = NULL;
    temp.files.loadfolder(temp.filePath, 0);
    if (temp.files.size() == 0) {
      TE_echo("prob: (npcserver) cannot write the bucket image file as" SPC temp.filePath @ ". Ensure that both the configuration and the (npcserver)'s rights are correct");
    } else {
      TE_echo("bucket image saved to" SPC temp.filePath);
    }
  }
 
  // check if the pencil image is on the server
  // if it is not, attempt to write it
  temp.filePath = TE_DIR_IMAGES @ "tileeditor_pencil_1-1.png";
  temp.files.loadfolder(temp.filePath, 0);
  if (temp.files.size() == 0) {
    temp.data = base64decode(TE_PENCIL_DATA);
    temp.data.savestring(temp.filePath, 0);
    temp.files = NULL;
    temp.files.loadfolder(temp.filePath, 0);
    if (temp.files.size() == 0) {
      TE_echo("prob: (npcserver) cannot write the pencil image file as" SPC temp.filePath @ ". Ensure that both the configuration and the (npcserver)'s rights are correct");
    } else {
      TE_echo("pencil image saved to" SPC temp.filePath);
    }
  }
}
 
function onActionServerSide() {
  // if the script is configured to be staff only
  // detect non-staff usage attempts here
  if (TE_ONLYSTAFF) {
    temp.staffList = serveroptions.staff.lower().tokenize(",");
    if (!(player.account.lower() in temp.staffList) 
     && (player.communityname == ""
      || !(player.communityname.lower() in temp.staffList))) {
      TE_echo(format("%s tried to use the tile editor, but is not staff", player.account));
      return;
    }
  }
 
  replySwitch:
  switch (params[0]) {
    // when the program is triggered to open
    // it triggers the server with the REQUESTUSE reply
    // the server returns a "1" or "0"
    case RPL.REQUESTUSE: {
      if (TE_NEEDRIGHTS) {
        if (player.level.name.ends(".gmap")) {
          temp.mapPartLevelName = player.level.getmappartfile(player.x, player.y);
          temp.allowUse = player.hasright("r", temp.mapPartLevelName);
        } else {
          temp.allowUse = player.hasright("r", player.level.name);
        }
      } else {
        temp.allowUse = true;
      }
      player.triggerclient("gui", this.name, RPL.REQUESTUSE, temp.allowUse, TE_VERSION);
      break;
    }
 
    // determine if the player is allowed to edit the level
    // if so, paste the data to the levels tile board
    // otherwise, send an ERROR reply
    case RPL.PLACETILES: {
      if (params[1].size() == 0)
        break;
      temp.data = params;
      temp.dataSize0 = temp.data[1].size();
      temp.dataSize1 = temp.data[1][0].size();
      maxlooplimit = 100000;
 
      if (TE_NEEDRIGHTS) {
        if (player.level.name.ends(".gmap")) {
          temp.yMax = temp.data[3] + temp.dataSize1 + 64;
          temp.xMax = temp.data[2] + temp.dataSize0 + 64;
          for (temp.i = temp.data[3]; temp.i <= temp.yMax; temp.i += 64) {
            for (temp.j = temp.data[2]; temp.j <= temp.xMax; temp.j += 64) {
              temp.mapPartLevelName = player.level.getmappartfile(temp.j, temp.i);
              if (temp.mapPartLevelName != ""
               && !player.hasright("w", temp.mapPartLevelName)) {
                player.triggerclient("gui", this.name, RPL.ERROR, "You do not have rights to edit" SPC temp.mapPartLevelName);
                break replySwitch;
              }
            }
          }
        } else {
          if (!player.hasright("w", player.level.name)) {
            player.triggerclient("gui", this.name, RPL.ERROR, "You do not have rights to edit" SPC player.level.name);
            break;
          }
        }
      }
 
      for (temp.y = 0; temp.y < temp.dataSize0; temp.y++) {
        if (temp.data[4] == 0 && temp.data[1][temp.y].index(-1) != -1) {
          do {
            temp.ind = temp.data[1][temp.y].index(-1);
            temp.data[1][temp.y][temp.ind] = 0;
          } while (temp.data[1][temp.y].index(-1) != -1);
        }
        for (temp.x = 0; temp.x < temp.dataSize1; temp.x++) {
          temp.tile = temp.data[1][temp.y][temp.x];
          if (temp.tile == -2)
            continue;
          tilelayers[temp.data[4]].tiles[temp.x + temp.data[2], temp.y + temp.data[3]] = temp.tile;
        }
      }
      tilelayers[temp.data[4]].updateboard2(temp.data[2], temp.data[3], temp.data[1][0].size(), temp.data[1].size());
      break;
    }
 
    // provided the player is allowed to READ the file
    // this loads the file data and sends it to the client
    case RPL.LOADCLIPBOARDFILE: {
      if (!fileexists(TE_DIR_CLIPBOARD @ params[1]))
        break;
      temp.obj = {};
      temp.obj.loadlines(TE_DIR_CLIPBOARD @ params[1]);
      if (temp.obj.size() == 0) {
        player.triggerclient("gui", this.name, RPL.ERROR, "The (npcserver) lacks the rights to read the file");
        return;
      }
      player.triggerclient("gui", this.name, RPL.LOADCLIPBOARDFILE, temp.obj, params[1], getfilemodtime(TE_DIR_CLIPBOARD @ params[1]));
      break;
    }
 
    // provided the player is allowed to WRITE the file
    // this saves the file data
    case RPL.SAVECLIPBOARDFILE: {
      temp.fileBase = extractfilebase(params[2]);
      if (temp.fileBase == "")
        break;
      temp.filePath = TE_DIR_CLIPBOARD @ extractfilepath(params[2]);
      temp.filePath = temp.filePath @ temp.fileBase @ ".txt";
      if (TE_NEEDRIGHTS && !player.hasright("w", temp.filePath)) {
        player.triggerclient("gui", this.name, RPL.ERROR, "You do not have rights for" SPC temp.filePath);
        return;
      }
      params[1].savelines(temp.filePath, 0);
      temp.files.loadfolder(temp.filePath, 0);
      if (temp.files.size() == 0) {
        player.triggerclient("gui", this.name, RPL.ERROR, "The (npcserver) lacks the rights to create the file");
        return;
      }
      player.triggerclient("gui", this.name, RPL.SAVECLIPBOARDFILE, temp.filePath);
      break;
    }
 
    // provided the player is allowed to WRITE the file
    // this deletes the file
    case RPL.DELETECLIPBOARDFILE: {
      temp.filePath = TE_DIR_CLIPBOARD @ params[1];
      if (TE_NEEDRIGHTS && !player.hasright("w", temp.filePath)) {
        player.triggerclient("gui", this.name, RPL.ERROR, "You do not have rights for" SPC temp.filePath);
        return;
      }
      if (!fileexists(temp.filePath)) {
        player.triggerclient("gui", this.name, RPL.ERROR, temp.filePath SPC "does not exist");
        return;
      }
      deletefile(temp.filePath);
      temp.files.loadfolder(temp.filePath, 0);
      if (temp.files.size() == 1)
        player.triggerclient("gui", this.name, RPL.ERROR, "The (npcserver) lacks the rights to delete the file");
    }
 
    // will read all clipboard files which the player
    // is allowed to read, and return the list to the client
    case RPL.LISTCLIPBOARDFOLDER: {
      temp.obj = {};
      temp.obj.loadfolder(TE_DIR_CLIPBOARD @ "*", 1);
      if (TE_NEEDRIGHTS) {
        temp.list = {};
        for (temp.o: temp.obj) {
          temp.filePath = TE_DIR_CLIPBOARD @ temp.o;
          if (player.hasright("r", temp.filePath))
            temp.list.add(temp.o);
        }
      } else {
        temp.list = temp.obj;
      }
      player.triggerclient("gui", this.name, RPL.LISTCLIPBOARDFOLDER, temp.list);
      break;
    }
 
    // checks for an invalid filename
    // if not, reply with ERROR
    // checks if the player is allowed to WRITE the file
    // if not, reply with ERROR
    // checks if the file already exists
    // if not, write
    // if so, checks if the override parameter is set
    // if not, reply with ERROR
    // if so, write
    case RPL.CREATENEWLEVEL: {
      temp.fileBase = extractfileBase(params[1]).lower();
      temp.fileExt = extractfileext(params[1]).lower();
      if (temp.fileBase == ""
       || temp.fileExt != ".nw") {
        player.triggerclient("gui", this.name, RPL.ERROR, "Invalid file name");
        return;
      }
      temp.filePath = extractfilepath(params[1]).lower() @ temp.fileBase @ temp.fileExt;
      if (TE_NEEDRIGHTS && !player.hasright("w", temp.filePath)) {
        player.triggerclient("gui", this.name, RPL.ERROR, "You do not have rights for" SPC temp.filePath);
        return;
      }
      if (fileexists(temp.filePath) && !params[2]) {
        player.triggerclient("gui", this.name, RPL.ERROR, "That file already exists! Type the same thing in again to overwrite");
        return;
      }
      temp.lines = {
        "GLEVNW01"
      };
      temp.lines.savelines(temp.filePath, 0);
      temp.files.loadfolder(temp.filePath, 0);
      if (temp.files.size() == 0) {
        player.triggerclient("gui", this.name, RPL.ERROR, "The (npcserver) lacks the rights to create the file");
        return;
      }
      player.triggerclient("gui", this.name, RPL.CREATENEWLEVEL, temp.filePath);
      break;
    }
  }
}
 
/**
 * functions
 */
 
// just a simple, customized output function
function TE_echo(temp.text) {
  echo(this.name @ ":" SPC temp.text);
}
 
//#CLIENTSIDE
 
// numeric network replies
// these MUST match the serverside
enum RPL {
  NONE,
  ERROR,
  REQUESTUSE,
  PLACETILES,
  LISTCLIPBOARDFOLDER,
  LOADCLIPBOARDFILE,
  SAVECLIPBOARDFILE,
  DELETECLIPBOARDFILE,
  CREATENEWLEVEL
}
 
// numeric paste modes
enum PASTEMODE {
  NONE,
  NORMAL,
  FILL
}
 
/**
 * events
 */
 
function onCreated() {
  // if the script is updated while someone
  // is using the editor, and they are using the grid
  // the grid disappears because all the images
  // of the script become reset.
  // so, if the grid is enabled, we re-draw it here
  if (GuiWindow_TileEditor.grid)
    updateGridEffect();
}
 
function onPlayerEnters() {
  // if the program is open, and the player enters a new level
  // exit the program, to prevent problems such as 
  // undo and redo affecting the wrong level
  if (GuiWindow_TileEditor != NULL
   && GuiWindow_TileEditor.editingLevel != (isonmap ? player.gmap.name : player.level.name)) {
    onCloseEditor();
  }
}
 
function onActionClientSide() {
  switch (params[0]) {
    // depending which gui is open (Open or Save)
    // loads the tree view of  the gui with
    // the clipboard file list supplied by server
    case RPL.LISTCLIPBOARDFOLDER: {
      if (GuiWindow_TileEditor_FileOpen != NULL) {
        GuiTreeView_TileEditor_FileOpen.clearnodes();
        for (temp.fileName: params[1]) {
          temp.fileExt = extractfileext(temp.fileName);
          if (temp.fileExt != ".txt" && temp.fileExt != "")
            continue;
          with (GuiTreeView_TileEditor_FileOpen.addnodebypath(temp.fileName, "/")) {
            image = selectedimage = temp.fileExt == "" ? 1 : 2;
          }
        }
      }
      if (GuiWindow_TileEditor_FileSave != NULL) {
        GuiTreeView_TileEditor_FileSave.clearnodes();
        for (temp.fileName: params[1]) {
          temp.fileExt = extractfileext(temp.fileName);
          if (temp.fileExt != ".txt" && temp.fileExt != "")
            continue;
          with (GuiTreeView_TileEditor_FileSave.addnodebypath(temp.fileName, "/")) {
            image = selectedimage = temp.fileExt == "" ? 1 : 2;
          }
        }
      }
      break;
    }
 
    // sets clipboard from server data
    case RPL.LOADCLIPBOARDFILE: {
      temp.lines = {};
      for (temp.line: params[1])
        temp.lines.add(temp.line.tokenize());
      temp.extent = {
        temp.lines[0].size(),
        temp.lines.size()
      };
      if (GuiWindow_TileEditor_FileOpen != NULL) {
        GuiText_TileEditor_FileOpen_Title_Value.text = params[2];
        GuiText_TileEditor_FileOpen_Extent_Value.text = temp.extent[0] @ "," SPC temp.extent[1];
        GuiText_TileEditor_FileOpen_Created_Value.text = converttimetostring(params[3]);
        GuiStretch_TileEditor_FileOpen.clientwidth =
        GuiDrawingPanel_TileEditor_FileOpen_Preview.width = temp.extent[0] * 16;
        GuiStretch_TileEditor_FileOpen.clientheight =
        GuiDrawingPanel_TileEditor_FileOpen_Preview.height = temp.extent[1] * 16;
        GuiDrawingPanel_TileEditor_FileOpen_Preview.clearall();
        for (temp.i = 0; temp.i < temp.extent[0]; temp.i++) {
          for (temp.j = 0; temp.j < temp.extent[1]; temp.j++) {
            temp.tileXY = getTileXY(temp.lines[temp.j][temp.i]);
            GuiDrawingPanel_TileEditor_FileOpen_Preview.drawimagerectangle(
              temp.i * 16, 
              temp.j * 16, 
              GuiWindow_TileEditor.tilesetImage, 
              temp.tileXY[0], 
              temp.tileXY[1], 
              16, 
              16
            );
          }
          GuiWindow_TileEditor_FileOpen.clipboard = temp.lines;
        }
      }
      if (GuiWindow_TileEditor_FileSave != NULL) {
        GuiText_TileEditor_FileSave_Title_Value.text = params[2];
        GuiText_TileEditor_FileSave_Extent_Value.text = temp.extent[0] @ "," SPC temp.extent[1];
        GuiText_TileEditor_FileSave_Created_Value.text = converttimetostring(params[3]);
        GuiStretch_TileEditor_FileSave.clientwidth =
        GuiDrawingPanel_TileEditor_FileSave_Preview.width = temp.extent[0] * 16;
        GuiStretch_TileEditor_FileSave.clientheight =
        GuiDrawingPanel_TileEditor_FileSave_Preview.height = temp.extent[1] * 16;
        GuiDrawingPanel_TileEditor_FileSave_Preview.clearall();
        for (temp.i = 0; temp.i < temp.extent[0]; temp.i++) {
          for (temp.j = 0; temp.j < temp.extent[1]; temp.j++) {
            temp.tileXY = getTileXY(temp.lines[temp.j][temp.i]);
            GuiDrawingPanel_TileEditor_FileSave_Preview.drawimagerectangle(
              temp.i * 16, 
              temp.j * 16, 
              GuiWindow_TileEditor.tilesetImage, 
              temp.tileXY[0], 
              temp.tileXY[1], 
              16, 
              16
            );
          }
        }
      }
      break;
    }
 
    // just a reply from the server indicating that
    // the clipboard file successfully saved
    case RPL.SAVECLIPBOARDFILE: {
      shared.adminmessage(params[1] SPC "is saved");
      break;
    }
 
    // generic error reply from the server
    // if an error message is supplied, display it
    // otherwises display the default error message
    case RPL.ERROR: {
      if (params[1] != NULL)
        shared.adminmessage(params[1]);
      else
        shared.adminmessage("An unexpected error occurred");
      break;
    }
 
    // just a reply from the server indicating that
    // the new level has been created
    case RPL.CREATENEWLEVEL: {
      shared.adminmessage(params[1] SPC "is saved");
      GuiWindow_TileEditor.newLevelOverwrite = NULL;
      break;
    }
  }
}
 
function ChatBar.onAction() {
  if (ChatBar.text.lower().trim() == "/te")
    onRequestUse();
}
 
function GuiWindow_TileEditor.onResize(temp.newWidth, temp.newHeight) {
  // there is a bit of weird code here because
  // the behavior of the window gui changes
  // after it becomes external
  if (GuiWindow_TileEditor.isexternal) {
    GuiMenu_TileEditor.x = 0;
    GuiMenu_TileEditor.y = 0;
    GuiMenu_TileEditor.width = temp.newWidth;
    GuiScroll_TileEditor.x = 0;
    GuiScroll_TileEditor.y = 20;
    GuiScroll_TileEditor.width = temp.newWidth;
    GuiScroll_TileEditor.height = temp.newHeight - 20;
  } else {
    GuiMenu_TileEditor.width = temp.newWidth - 12;
    GuiScroll_TileEditor.width = temp.newWidth - 12;
    GuiScroll_TileEditor.height = temp.newHeight - 50;
  }
}
 
function GuiWindow_TileEditor.onCloseQuery() {
  onCloseEditor();
}
 
function GuiMenu_TileEditor.onSelect(temp.menuName, temp.entryId, temp.entryText, temp.entryIndex) {
  temp.menu = GuiMenu_TileEditor.findmenu(temp.menuName);
  switch (temp.menuName) {
    case "File": {
      switch (temp.entryId) {
        case 0: {
          // display a text edit gui for the player to type in
          // the file path they would like to save the new
          // level as
          with (GuiWindow_TileEditor) {
            new GuiTextEditCtrl("GuiTextEdit_TileEditor_NewLevel") {
              profile = GuiBlueTextEditProfile;
              x = GuiWindow_TileEditor.wasExternal ? 0 : 6;
              y = GuiWindow_TileEditor.wasExternal ? 20 : 44;
              width = GuiWindow_TileEditor.width - x * 2;
              height = 20;
              text = "levels/new.nw";
              makefirstresponder(true);
              setselection(0, text.length());
            }
          }
          break;
        }
        case 1: {
          showFileOpenGui();
          break;
        }
        case 2: {
          showFileSaveGui();
          break;
        }
        case 3: {
          onCloseEditor();
          break;
        }
      }
      break;
    }
    case "Edit": {
      switch (temp.entryId) {
        case 0: {
          editorUndo();
          break;
        }
        case 1: {
          editorRedo();
          break;
        }
      }
      break;
    }
    case "View": {
      switch (temp.entryId) {
        case 0: {
          GuiWindow_TileEditor.fadeLayers = !GuiWindow_TileEditor.fadeLayers;
          if (GuiWindow_TileEditor.fadeLayers)
            temp.menu.setrowbyid(0, "*Fade inactive layers");
          else
            temp.menu.setrowbyid(0, "Fade inactive layers");
          updateFadeEffect();
          break;
        }
        case 1: {
          GuiWindow_TileEditor.grid = !GuiWindow_TileEditor.grid;
          if (GuiWindow_TileEditor.grid)
            temp.menu.setrowbyid(1, "*Grid");
          else
            temp.menu.setrowbyid(1, "Grid");
          updateGridEffect();
          break;
        }
        case 2: {
          GuiWindow_TileEditor.extraCursorInfo = !GuiWindow_TileEditor.extraCursorInfo;
          if (GuiWindow_TileEditor.extraCursorInfo) {
            temp.menu.setrowbyid(2, "*Extra cursor info");
          } else {
            hideimgs(200, 204);
            temp.menu.setrowbyid(2, "Extra cursor info");
          }
          break;
        }
      }
      break;
    }
    case "Tileset": {
      switch (temp.entryId) {
        // displays a text edit gui for the player
        // to type in what image to use as the tileset
        // in the gui
        case 0: {
          with (GuiWindow_TileEditor) {
            new GuiTextEditCtrl("GuiTextEdit_TileEditor_Tileset") {
              profile = GuiBlueTextEditProfile;
              x = GuiWindow_TileEditor.wasExternal ? 0 : 6;
              y = GuiWindow_TileEditor.wasExternal ? 20 : 44;
              width = GuiWindow_TileEditor.width - x * 2;
              height = 20;
              text = "pics1.png";
              makefirstresponder(true);
              setselection(0, text.length());
            }
          }
          break;
        }
        // resets the gui tileset image to the current tiledefs
        case 1: {
          with (GuiDrawingPanel_TileEditor) {
            clearall();
            GuiWindow_TileEditor.tilesetImage = "TILES";
            drawimage(0, 0, GuiWindow_TileEditor.tilesetImage);
          }
          break;
        }
      }
      break;
    }
    case "Layer": {
      // change current editing layer based on which
      // entryId was selected
      temp.menu.setrowbyid(GuiWindow_TileEditor.editingLayer, GuiWindow_TileEditor.editingLayer + 1);
      temp.menu.setrowbyid(temp.entryId, "*" @ (temp.entryId + 1));
      GuiWindow_TileEditor.editingLayer = temp.entryId;
      GuiWindow_TileEditor.text = " Tile Editor v" @ GuiWindow_TileEditor.version SPC "(Layer" SPC GuiWindow_TileEditor.editingLayer + 1 @ ")";
      updateFadeEffect();
      break;
    }
    case "Options": {
      switch (temp.entryId) {
        case 0: {
          GuiWindow_TileEditor.isexternal = !GuiWindow_TileEditor.isexternal;
          GuiWindow_TileEditor.wasExternal = true;
          if (GuiWindow_TileEditor.isexternal)
            temp.menu.setrowbyid(0, "*External");
          else
            temp.menu.setrowbyid(0, "External");
          GuiWindow_TileEditor.trigger("onResize", GuiWindow_TileEditor.width, GuiWindow_TileEditor.height);
          break;
        }
        case 1: {
          openurl("http://forums.graalonline.com/forums/showthread.php?t=134267103");
          break;
        }
        case 2: {
          temp.req = requesturl("http://forums.graalonline.com/forums/showthread.php?t=134267103");
          temp.time = timevar2;
          waitfor(temp.req, "onReceiveData", 10);
          if (timevar2 - temp.time < 10) {
            temp.position = temp.req.fulldata.pos("[VERSION]") + 9;
            temp.currentVersionString = temp.req.fulldata.substring(temp.position, temp.req.fulldata.pos("[/VERSION]") - position);
            if (GuiWindow_TileEditor.version == temp.currentVersionString)
              shared.adminmessage("You are using the newest version already");
            else
              shared.adminmessage("A new version is available:\n" @ temp.currentVersionString);
          } else {
            shared.adminmessage("Version check timed out");
          }
          break;
        }
      }
      break;
    }
  }
}
 
function GuiButton_TileEditor_PasteMode.onAction() {
  // toggles between the fill pastemode and the normal pastemode
  if (GuiWindow_TileEditor.pasteMode == PASTEMODE.NORMAL) {
    GuiWindow_TileEditor.pasteMode = PASTEMODE.FILL;
    GuiShowImg_TileEditor_PasteMode.image = "tileeditor_pencil_1-1.png";
  } elseif (GuiWindow_TileEditor.pasteMode == PASTEMODE.FILL) {
    GuiWindow_TileEditor.pasteMode = PASTEMODE.NORMAL;
    GuiShowImg_TileEditor_PasteMode.image = "tileeditor_bucket_1-1.png";
  }
}
 
function GuiWindow_TileEditor.onHide() {
  // detect when the editor is closed from external window
  // it looks weird because external stuff is weird
  if (GuiWindow_TileEditor.isexternal && !GuiWindow_TileEditor.visible)
    onCloseEditor();
}
 
function GuiTextEdit_TileEditor_NewLevel.onAction() {
  // triggers the server requesting to create a new level
  // at the specified filepath
  temp.text = GuiTextEdit_TileEditor_NewLevel.text;
  triggerserver("gui", this.name, RPL.CREATENEWLEVEL, temp.text, (temp.text.lower() == GuiWindow_TileEditor.newLevelOverwrite));
  GuiWindow_TileEditor.newLevelOverwrite = temp.text.lower();
  GuiTextEdit_TileEditor_NewLevel.destroy();
}
 
function GuiTextEdit_TileEditor_Tileset.onAction() {
  // draw the gui tileset as the image specified
  with (GuiDrawingPanel_TileEditor) {
    clearall();
    drawimage(0, 0, GuiTextEdit_TileEditor_Tileset.text);
  }
  GuiWindow_TileEditor.tilesetImage = GuiTextEdit_TileEditor_Tileset.text;
  GuiTextEdit_TileEditor_Tileset.destroy();
}
 
function GuiDrawingPanel_TileEditor.onMouseDown(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // beginning of gui tile selection
  GuiDrawingPanel_TileEditor.mouseWasDown = true;
  GuiDrawingPanel_TileEditor.mouselock(0);
  temp.coords = GuiDrawingPanel_TileEditor.globaltolocalcoord({temp.xMouse, temp.yMouse});
  GuiWindow_TileEditor.tileSelectStart = 
  GuiWindow_TileEditor.tileSelectEnd = {temp.coords[0] - temp.coords[0] % 16, temp.coords[1] - temp.coords[1] % 16};
  drawGuiSelect();
}
 
function GuiDrawingPanel_TileEditor.onMouseUp(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  if (!GuiDrawingPanel_TileEditor.mouseWasDown)
    return;
  // ending of gui tile selection
  GuiDrawingPanel_TileEditor.mouseWasDown = false;
  if (Gui_TileEditor_Select != NULL)
    Gui_TileEditor_Select.destroy();
  setClipboardFromGui();
  GraalControl.makefirstresponder(true);
}
 
function GuiDrawingPanel_TileEditor.onMouseDragged(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // update gui tile selection with new coordinates after drag
  temp.coords = GuiDrawingPanel_TileEditor.globaltolocalcoord({temp.xMouse, temp.yMouse});
  temp.scrollcoords = GuiWindow_TileEditor.globaltolocalcoord({temp.xMouse, temp.yMouse});
  if (temp.scrollcoords[0] < GuiScroll_TileEditor.x + 32)
    temp.scrollx = temp.scrollcoords[0] - GuiScroll_TileEditor.x - 32;
  elseif (temp.scrollcoords[0] > GuiScroll_TileEditor.x + GuiScroll_TileEditor.width - 64)
    temp.scrollx = temp.scrollcoords[0] - GuiScroll_TileEditor.x - GuiScroll_TileEditor.width + 64;
  if (temp.scrollcoords[1] < GuiScroll_TileEditor.y + 32)
    temp.scrolly = temp.scrollcoords[1] - GuiScroll_TileEditor.y - 32;
  elseif (temp.scrollcoords[1] > GuiScroll_TileEditor.y + GuiScroll_TileEditor.height - 64)
    temp.scrolly = temp.scrollcoords[1] - GuiScroll_TileEditor.y - GuiScroll_TileEditor.height + 64;
  if (temp.scrollx != NULL || temp.scrolly != NULL)
    GuiScroll_TileEditor.scrolldelta(int(temp.scrollx / 8.0), int(temp.scrolly / 8.0));
  GuiWindow_TileEditor.tileSelectEnd = {temp.coords[0] - temp.coords[0] % 16, temp.coords[1] - temp.coords[1] % 16};
  drawGuiSelect();
}
 
function GuiTreeView_TileEditor_FileOpen.onSelect(temp.node, temp.nodeSlashPath, temp.nodeDotPath) {
  // if the selection ends in .txt, request to load the clipboard file
  if (!temp.node.ends(".txt"))
    return;
  triggerserver("gui", this.name, RPL.LOADCLIPBOARDFILE, temp.nodeSlashPath);
  GuiTreeView_TileEditor_FileOpen.selectedSlashPath = temp.nodeSlashPath;
}
 
function GuiButton_TileEditor_FileOpen_Delete.onAction() {
  // if the filepath is something, trigger server to delete it
  // i might add a confirmation box option eventually
  temp.filePath = GuiTreeView_TileEditor_FileOpen.selectedSlashPath;
  if (temp.filePath != NULL)
    triggerserver("gui", this.name, RPL.DELETECLIPBOARDFILE, temp.filePath);
}
 
function GuiButton_TileEditor_FileOpen_Open.onAction() {
  // load the clipboard file data into the clipboard
  if (GuiWindow_TileEditor_FileOpen.clipboard == NULL)
    return;
  GuiWindow_TileEditor.clipboard = GuiWindow_TileEditor_FileOpen.clipboard;
  drawClipboardPreview();
  GuiWindow_TileEditor_FileOpen.destroy();
}
 
function GuiButton_TileEditor_FileOpen_Cancel.onAction() {
  GuiWindow_TileEditor_FileOpen.destroy();
}
 
function GuiTreeView_TileEditor_FileSave.onSelect(temp.node, temp.nodeSlashPath, temp.nodeDotPath) {
  // if the path ends with .txt, trigger the server
  // to load the file
  if (!temp.node.ends(".txt"))
    return;
  triggerserver("gui", this.name, RPL.LOADCLIPBOARDFILE, temp.nodeSlashPath);
  GuiTextEdit_TileEditor_FileSave_FilePath.text = temp.nodeSlashPath;
}
 
function GuiButton_TileEditor_FileSave_Delete.onAction() {
  // if  the filepath is something, triggerserver to delete it
  temp.filePath = GuiTextEdit_TileEditor_FileSave_FilePath.text;
  if (temp.filePath != NULL)
    triggerserver("gui", this.name, RPL.DELETECLIPBOARDFILE, temp.filePath);
}
 
function GuiButton_TileEditor_FileSave_Save.onAction() {
  // if the filepath is something, triggerserver to save
  // the clipboard file
  temp.filePath = GuiTextEdit_TileEditor_FileSave_FilePath.text;
  if (temp.filePath.length() == 0)
    return;
  triggerserver("gui", this.name, RPL.SAVECLIPBOARDFILE, GuiWindow_TileEditor.clipboard, temp.filePath);
  GuiWindow_TileEditor_FileSave.destroy();
}
 
function GuiButton_TileEditor_FileSave_Cancel.onAction() {
  GuiWindow_TileEditor_FileSave.destroy();
}
 
function GraalControl.onMouseDown(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // if the preview gui exists, destroy it
  // because either the program is dead or
  // we are selecting new tiles from the level
  if (GuiShowImg_TileEditor_ClipboardPreview != NULL)
    GuiShowImg_TileEditor_ClipboardPreview.destroy();
  // if the program isn't open, forget it
  if (GuiWindow_TileEditor == NULL)
    return;
  // beginning of tile selection from level
  GuiWindow_TileEditor.tileSelectStart = 
  GuiWindow_TileEditor.tileSelectEnd = {int(mousex), int(mousey)};
  drawSelect();
  if (!GuiWindow_TileEditor.isexternal)
    GuiWindow_TileEditor.visible = false;
}
 
function GraalControl.onRightMouseDown(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // if the program isn't open or
  // no tiles are in the clipboard, forget it
  if (GuiWindow_TileEditor == NULL
   || GuiShowImg_TileEditor_ClipboardPreview == NULL
   || GuiWindow_TileEditor.clipboard == NULL)
    return;
 
  // make the preview bluey so there's some visual
  // indication that the paste is happening
  GuiShowImg_TileEditor_ClipboardPreview.blue = 1.0;
  // disable flickering while pasting too
  GuiDrawingPanel_TileEditor_ClipboardPreview.flickering = false;
 
  temp.mx = int(mousex);
  temp.my = int(mousey);
 
  // pastemode normal is just what you expect from your standard
  // tile editor. the tiles you see in the clipboard preview
  // are pushed to the stack of undo/redo and the clipboard
  // data is sent to the server as a request to paste tiles
  if (GuiWindow_TileEditor.pasteMode == PASTEMODE.NORMAL) {
    temp.historyObject = {
      temp.mx,
      temp.my,
      GuiWindow_TileEditor.editingLayer,
      NULL,
      GuiWindow_TileEditor.clipboard
    };
    for (temp.y = 0; temp.y < GuiWindow_TileEditor.clipboard.size(); temp.y++) {
      temp.row = {};
      for (temp.x = 0; temp.x < GuiWindow_TileEditor.clipboard[0].size(); temp.x++) {
        if (GuiWindow_TileEditor.editingLayer == 0)
          temp.tile = tiles[temp.x + temp.mx, temp.y + temp.my];
        else
          temp.tile = tilelayers[GuiWindow_TileEditor.editingLayer].tiles[temp.x + temp.mx, temp.y + temp.my];
        temp.row.add(temp.tile);
      }
      temp.historyObject[3].add(temp.row);
    }
    if (GuiWindow_TileEditor.history == NULL)
      GuiWindow_TileEditor.history = {};
    GuiWindow_TileEditor.history = GuiWindow_TileEditor.history.subarray(GuiWindow_TileEditor.historyIndex);
    GuiWindow_TileEditor.historyIndex = 0;
    GuiWindow_TileEditor.history.insert(0, temp.historyObject);
    triggerserver("gui", this.name, RPL.PLACETILES, GuiWindow_TileEditor.clipboard, temp.mx, temp.my, GuiWindow_TileEditor.editingLayer);
 
  // pastemode fill implements a flood fill algorithm
  // the computations are done on the clientside, here,
  // then a standard tile paste request is sent to the
  // server with the rectangle which contains the flooded
  // tiles
  } elseif (GuiWindow_TileEditor.pasteMode == PASTEMODE.FILL) {
    if (GuiWindow_TileEditor.editingLayer == 0) {
      temp.floodedArea = getFloodedAreaZero(temp.mx, temp.my);
    } else {
      temp.tileLayer = tilelayers[GuiWindow_TileEditor.editingLayer];
      temp.floodedArea = getFloodedAreaLayer(temp.tileLayer, temp.mx, temp.my);
    }
    temp.pasteX = temp.floodedArea[0];
    temp.pasteY = temp.floodedArea[1];
    temp.sizeY = temp.floodedArea[3] - temp.pasteY + 1;
    temp.sizeX = temp.floodedArea[2] - temp.pasteX + 1;
    temp.pasteData = new [temp.sizeY][temp.sizeX];
    temp.historyData = {};
    maxlooplimit = 100000;
    for (temp.y = 0; temp.y < temp.sizeY; temp.y++) {
      temp.row = {};
      for (temp.x = 0; temp.x < temp.sizeX; temp.x++) {
        if ((@{temp.pasteX + temp.x, temp.pasteY + temp.y}) in temp.floodedArea) {
          temp.pasteData[temp.y][temp.x] = GuiWindow_TileEditor.clipboard[(temp.pasteY - temp.my + temp.y) % GuiWindow_TileEditor.clipboard.size()][(temp.pasteX - temp.mx + temp.x) % GuiWindow_TileEditor.clipboard[0].size()];
        } else {
          temp.pasteData[temp.y][temp.x] = -2;
        }
        if (GuiWindow_TileEditor.editingLayer == 0)
          temp.row.add(tiles[temp.pasteX + temp.x, temp.pasteY + temp.y]);
        else
          temp.row.add(temp.tileLayer.tiles[temp.pasteX + temp.x, temp.pasteY + temp.y]);
      }
      temp.historyData.add(temp.row);
    }
    temp.historyObject = {
      temp.pasteX,
      temp.pasteY,
      GuiWindow_TileEditor.editingLayer,
      temp.historyData,
      temp.pasteData
    };
    if (GuiWindow_TileEditor.history == NULL)
      GuiWindow_TileEditor.history = {};
    GuiWindow_TileEditor.history = GuiWindow_TileEditor.history.subarray(GuiWindow_TileEditor.historyIndex);
    GuiWindow_TileEditor.historyIndex = 0;
    GuiWindow_TileEditor.history.insert(0, temp.historyObject);
    triggerserver("gui", this.name, RPL.PLACETILES, temp.pasteData, temp.pasteX, temp.pasteY, GuiWindow_TileEditor.editingLayer);
  }
}
 
function GraalControl.onRightMouseUp(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // if the program isn't open or the preview isn't existing
  // we don't care
  if (GuiWindow_TileEditor == NULL
   || GuiShowImg_TileEditor_ClipboardPreview == NULL)
    return;
  // reset the preview back to black to indicate that
  // you are no longer pasting tiles
  GuiShowImg_TileEditor_ClipboardPreview.blue = 0.0;
  // reenable flickering as well
  GuiDrawingPanel_TileEditor_ClipboardPreview.flickering = true;
  // set the preview position to the mouse position
  GuiShowImg_TileEditor_ClipboardPreview.x = screenx(int(mousex), 0) - 2;
  GuiShowImg_TileEditor_ClipboardPreview.y = screeny(0, int(mousey)) - 2;
}
 
function GraalControl.onMouseDragged(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // if the program isn't open we don't care
  if (GuiWindow_TileEditor == NULL)
    return;
  // level selection drag here
  // set the ending selection position to the mouse position
  GuiWindow_TileEditor.tileSelectEnd = {int(mousex), int(mousey)};
  drawSelect();
}
 
function GraalControl.onMouseUp(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  hideimgs(200, 204);
  // if the program isn't open forget it
  if (GuiWindow_TileEditor == NULL)
    return;
  // create the clipboard data from the level data
  setClipboardFromLevel();
  GuiWindow_TileEditor.visible = true;
}
 
function GraalControl.onMouseMove(temp.keyMod, temp.xMouse, temp.yMouse, temp.clickCount) {
  // if the preview doesn't exist we don't care
  if (GuiShowImg_TileEditor_ClipboardPreview == NULL)
    return;
  temp.mx = int(mousex);
  temp.my = int(mousey);
 
  // the preview follows the mouse
  GuiShowImg_TileEditor_ClipboardPreview.x = screenx(temp.mx, 0) - 2;
  GuiShowImg_TileEditor_ClipboardPreview.y = screeny(0, temp.my) - 2;
 
  // display the pastemode icon a little bit above
  with (findimg(201)) {
    layer = 0;
    image = GuiWindow_TileEditor.pasteMode == PASTEMODE.FILL ? "tileeditor_bucket_1-1.png" : "tileeditor_pencil_1-1.png";
    x = temp.mx;
    y = temp.my - 1.5;
  }
 
  // if the extra cursor information is disabled
  // there's no need to continue
  if (!GuiWindow_TileEditor.extraCursorInfo)
    return;
 
  // display the top left corner's x , y
  with (findimg(202)) {
    layer = 0;
    text = temp.mx @ ", " @ temp.my;
    zoom = 0.6;
    x = temp.mx - 1.5 - gettextwidth(zoom, "", "", text) / 32;
    y = temp.my;
  }
  temp.tw = GuiWindow_TileEditor.clipboard[0].size();
  temp.th = GuiWindow_TileEditor.clipboard.size();
  temp.tex = temp.mx + tw;
  temp.tey = temp.my + th;
 
  // display the bottom right corner's x , y
  with (findimg(203)) {
    layer = 0;
    text = (temp.tex - 1) @ ", " @ (temp.tey - 1);
    x = temp.tex + 0.5;
    y = temp.tey - 1;
    zoom = 0.6;
  }
 
  // display the clipboard's (width , height)
  with (findimg(204)) {
    layer = 0;
    text = "(" @ temp.tw @ ", " @ temp.th @ ")";
    zoom = 0.6;
    x = temp.mx + temp.tw / 2 - gettextwidth(zoom, "", "", text) / 32;
    y = temp.tey + 0.5;
  }
}
 
function GraalControl.onKeyDown(temp.keyCode, temp.keyText, temp.scanCode) {
  // if the program is closed we dont care
  if (GuiWindow_TileEditor == NULL)
    return;
  // prevent the program from doing 4 billion undo/redo's
  // when the user only wants to do one
  if (timevar2 - GuiWindow_TileEditor.lastUndoOrRedoTime < 0.2)
    return;
  switch (temp.keyCode) {
    case 602: { // undo
      editorUndo();
      break;
    }
    case 601: { // redo
      editorRedo();
      break;
    }
    default: {
      return;
    }
  }
  GuiWindow_TileEditor.lastUndoOrRedoTime = timevar2;
}
 
function GuiTextEdit_TileEditor_Tileset.onLoseFirstResponder() {
  // sleep for a moment for some reason
  // some weird bug otherwise
  sleep(0.1);
  GuiTextEdit_TileEditor_Tileset.destroy();
}
 
function GuiTextEdit_TileEditor_NewLevel.onLoseFirstResponder() {
  // sleep for a moment for some reason
  // some weird bug otherwise
  sleep(0.1);
  GuiTextEdit_TileEditor_NewLevel.destroy();
}
 
function onCloseEditor() {
  // change the cursor from crosshair to default
  GraalControl.cursor = "default";
  // disable fading effect
  GuiWindow_TileEditor.fadeLayers = false;
  updateFadeEffect();
  // disable grid effect
  GuiWindow_TileEditor.grid = false;
  updateGridEffect();
  // destroy the program
  if (GuiShowImg_TileEditor_ClipboardPreview != NULL)
    GuiShowImg_TileEditor_ClipboardPreview.destroy();
  if (GuiWindow_TileEditor_FileOpen != NULL)
    GuiWindow_TileEditor_FileOpen.destroy();
  if (GuiWindow_TileEditor_FileSave != NULL)
    GuiWindow_TileEditor_FileSave.destroy();
  GuiWindow_TileEditor.destroy();
  // hide cursor information
  hideimgs(200, 204);
}
 
function onRequestUse() {
  // send a request to use the editor in this level
  triggerserver("gui", this.name, RPL.REQUESTUSE);
  do {
    temp.time = timevar2;
    waitfor(this, "onActionClientSide", 15.0);
    if (timevar2 - temp.time > 15.0) {
      shared.adminmessage("Your request to use the editor timed out");
      return;
    }
  } while (params[0] != RPL.REQUESTUSE);
  // params is set once onActionClientSide event is triggered
  // params[1] is either "1" or "0"
  // that indicates whether the player is allowed to use
  // the editor in this level or not
  if (params[1])
    showGui(params[2]);
  else
    shared.adminmessage("You do not have rights to view this level");
}
 
/**
 * functions
 */
 
function showGui(temp.version) {
  // if the program is already existing
  // destroy it, so we can replace it
  if (GuiWindow_TileEditor != NULL)
    GuiWindow_TileEditor.destroy();
  new GuiWindowCtrl("GuiWindow_TileEditor") {
    profile = GuiBlueWindowProfile;
    canminimize = 
    canmaximize = false;
    closequery = true;
    width = 304;
    height = 304;
    x = screenwidth - width;
    text = " Tile Editor v" @ temp.version SPC "(Layer 1)";
 
    // initialize the program with the defaults
    this.version = temp.version;
    this.tilesetImage = "TILES";
    this.editingLayer = 0;
    this.editingLevel = isonmap ? player.gmap.name : player.level.name;
    this.pasteMode = PASTEMODE.NORMAL;
    new GuiShowImgCtrl("GuiShowImg_TileEditor_Icon") {
      useownprofile = true;
      profile.modal = false;
      x = 8;
      y = 4;
      width = 16;
      height = 16;
      image = "tileeditor_icon_1-1.png";
    }
    new GuiMenuCtrl("GuiMenu_TileEditor") {
      profile = GuiBlueTextEditProfile;
      x = 6;
      y = 24;
      width = 304 - x * 2;
      height = 20;
      clearmenus();
      with (addmenu("File")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        addrow(0, "New Level").hint = "Create a new Graal level at the specified filepath";
        addrow(1, "Open").hint = "Open a clipboard file saved on the server";
        addrow(2, "Save").hint = "Save the current selection as a clipboard file on the server";
        addrow(3, "Exit");
      }
      with (addmenu("Edit")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        temp.ctrlKey = getplatform() == "mac" ? "CMD" : "CTRL";
        addrow(0, "Undo").hint = "Undo the previous tile paste (" @ temp.ctrlKey @ "+Z)";
        addrow(1, "Redo").hint = "Redo the previous undone paste (" @ temp.ctrlKey @ "+Y)";
      }
      with (addmenu("View")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        addrow(0, "Fade inactive layers").hint = "Fades the inactive tilelayers, so you can focus on the layer you need to edit";
        addrow(1, "Grid").hint = "Forms a grid to measure and be more precise with tile placement";
        addrow(2, "Extra cursor info").hint = "Displays coordinates and selection size at the cursor";
      }
      with (addmenu("Tileset")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        addrow(0, "Use image").hint = "Show an image to select from, rather than tiledefs";
        addrow(1, "Use tiledefs").hint = "Show the current tiledefs to select from";
      }
      with (addmenu("Layer")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        for (temp.i = 0; temp.i < tilelayercount; temp.i++)
          addrow(temp.i, temp.i + 1).hint = "Change the current editing layer to" SPC (temp.i + 1);
        setrowbyid(0, "*1");
      }
      with (addmenu("Options")) {
        profile = GuiBluePopUpMenuProfile;
        textprofile = GuiBlueTextListProfile;
        temp.obj = addrow(0, "External");
        temp.obj.hint = "Toggle between using an internal window and an external window";
        GuiWindow_TileEditor.externalToggleMenuRowObj = temp.obj;
        addrow(1, "Help").hint = "Open an internet browser window/tab to the graalonline forum thread";
        addrow(2, "Check for update").hint = "Checks online if there is an updated version";
      }
      new GuiButtonCtrl("GuiButton_TileEditor_PasteMode") {
        profile = GuiBlueButtonProfile;
        x = 248;
        y = 0;
        width = 40;
        height = 20;
        text = "";
        new GuiShowImgCtrl("GuiShowImg_TileEditor_PasteMode") {
          useownprofile = true;
          profile.modal = false;
          image = "tileeditor_bucket_1-1.png";
          x = 12;
          y = 2;
        }
      }
    }
    new GuiScrollCtrl("GuiScroll_TileEditor") {
      profile = GuiBlueScrollProfile;
      x = 6;
      y = 44;
      width = 304 - x * 2;
      height = 304 - y - 6;
      hScrollBar = "alwaysOn";
      vScrollBar = "alwaysOn";
      new GuiDrawingPanel("GuiDrawingPanel_TileEditor") {
        width = 2048;
        height = 512;
        clearall();
        drawimage(0, 0, GuiWindow_TileEditor.tilesetImage);
        cursor = "crosshair";
      }
    }
  }
  // the cursor is changed to a crosshair while the
  // program is open
  GraalControl.cursor = "crosshair";
}
 
function drawGuiSelect() {
  // draws the tile selection box on the gui
 
  if (GuiWindow_TileEditor.tileSelectStart[0] <= GuiWindow_TileEditor.tileSelectEnd[0]) {
    temp.tsx = GuiWindow_TileEditor.tileSelectStart[0];
    temp.tex = GuiWindow_TileEditor.tileSelectEnd[0] + 16;
  } else {
    temp.tsx = GuiWindow_TileEditor.tileSelectEnd[0];
    temp.tex = GuiWindow_TileEditor.tileSelectStart[0] + 16;
  }
  if (GuiWindow_TileEditor.tileSelectStart[1] <= GuiWindow_TileEditor.tileSelectEnd[1]) {
    temp.tsy = GuiWindow_TileEditor.tileSelectStart[1];
    temp.tey = GuiWindow_TileEditor.tileSelectEnd[1] + 16;
  } else {
    temp.tsy = GuiWindow_TileEditor.tileSelectEnd[1];
    temp.tey = GuiWindow_TileEditor.tileSelectStart[1] + 16;
  }
 
  if (Gui_TileEditor_Select != NULL) {
    Gui_TileEditor_Select.x = temp.tsx;
    Gui_TileEditor_Select.y = temp.tsy;
    Gui_TileEditor_Select.width = temp.tex - temp.tsx;
    Gui_TileEditor_Select.height = temp.tey - temp.tsy;
    return;
  }
 
  with (GuiDrawingPanel_TileEditor) {
    new GuiControl("Gui_TileEditor_Select") {
      useownprofile = true;
      profile.cankeyfocus =
      profile.modal = false;
      profile.border = 3;
      x = temp.tsx;
      y = temp.tsy;
      width = temp.tex - temp.tsx;
      height = temp.tey - temp.tsy;
      red = 
      green =
      blue = 0.0;
    }
  }
}
 
function drawSelect() {
  // draws the tile selection box on the level
 
  if (GuiWindow_TileEditor.tileSelectStart[0] <= GuiWindow_TileEditor.tileSelectEnd[0]) {
    temp.tsx = GuiWindow_TileEditor.tileSelectStart[0];
    temp.tex = GuiWindow_TileEditor.tileSelectEnd[0] + 1.0;
  } else {
    temp.tsx = GuiWindow_TileEditor.tileSelectEnd[0];
    temp.tex = GuiWindow_TileEditor.tileSelectStart[0] + 1.0;
  }
  if (GuiWindow_TileEditor.tileSelectStart[1] <= GuiWindow_TileEditor.tileSelectEnd[1]) {
    temp.tsy = GuiWindow_TileEditor.tileSelectStart[1];
    temp.tey = GuiWindow_TileEditor.tileSelectEnd[1] + 1.0;
  } else {
    temp.tsy = GuiWindow_TileEditor.tileSelectEnd[1];
    temp.tey = GuiWindow_TileEditor.tileSelectStart[1] + 1.0;
  }
 
  with (findimg(200)) {
    layer = 0;
    polygon = {
      temp.tsx, temp.tsy,
      temp.tex, temp.tsy,
      temp.tex, temp.tey,
      temp.tsx, temp.tey
    };
    red = 0.0;
    green = 0.0;
    blue = 0.0;
    alpha = 0.6;
  }
 
  // display the pastemode icon
  with (findimg(201)) {
    layer = 0;
    image = GuiWindow_TileEditor.pasteMode == PASTEMODE.FILL ? "tileeditor_bucket_1-1.png" : "tileeditor_pencil_1-1.png";
    x = temp.tsx;
    y = temp.tsy - 1.5;
  }
 
  // display the top left corner x , y
  with (findimg(202)) {
    layer = 0;
    text = temp.tsx @ ", " @ temp.tsy;
    zoom = 0.6;
    x = temp.tsx - 1.5 - gettextwidth(zoom, "", "", text) / 32;
    y = temp.tsy;
  }
 
  // display the bottom right corner x , y
  with (findimg(203)) {
    layer = 0;
    text = (temp.tex - 1) @ ", " @ (temp.tey - 1);
    x = temp.tex + 0.5;
    y = temp.tey - 1;
    zoom = 0.6;
  }
 
  // display the (width , height)
  with (findimg(204)) {
    layer = 0;
    text = "(" @ (temp.tex - temp.tsx) @ ", " @ (temp.tey - temp.tsy) @ ")";
    zoom = 0.6;
    x = temp.tsx + (temp.tex - temp.tsx) / 2 - gettextwidth(zoom, "", "", text) / 32;
    y = temp.tey + 0.5;
  }
}
 
function showFileOpenGui() {
  // if the Open or Save window already exists
  // destroy it to be replaced
  if (GuiWindow_TileEditor_FileSave != NULL)
    GuiWindow_TileEditor_FileSave.destroy();
  if (GuiWindow_TileEditor_FileOpen != NULL)
    GuiWindow_TileEditor_FileOpen.destroy();
  new GuiWindowCtrl("GuiWindow_TileEditor_FileOpen") {
    profile = GuiBlueWindowProfile;
    destroyonhide = true;
    canresize =
    canminimize = 
    canmaximize = false;
    width = 304;
    height = 304;
    x = screenwidth / 2 - width / 2;
    y = screenheight / 2 - height / 2;
    text = "Open";
    new GuiScrollCtrl("GuiScroll_TileEditor_FileOpen_Preview_Image") {
      x = 6;
      y = 24;
      profile = GuiBlueScrollProfile;
      width = 88;
      height = 88;
      hScrollBar = "alwaysOff";
      vScrollBar = "alwaysOff";
      new GuiStretchCtrl("GuiStretch_TileEditor_FileOpen") {
        width = 84;
        height = 84;
        new GuiDrawingPanel("GuiDrawingPanel_TileEditor_FileOpen_Preview") {
          clearall();
        }
      }
    }
    new GuiScrollCtrl("GuiScroll_TileEditor_FileOpen_Preview_Text") {
      profile = GuiBlueScrollProfile;
      x = 92;
      y = 24;
      width = 304 - x - 6;
      height = 88;
      hScrollBar = "dynamic";
      vScrollBar = "dynamic";
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Title") {
        profile = GuiBlueTextProfile;
        x = 4;
        height = 20;
        text = "Title:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Title_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        height = 20;
        text = "N / A";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Extent") {
        profile = GuiBlueTextProfile;
        x = 4;
        y = 18;
        height = 20;
        text = "Extent:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Extent_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        y = 18;
        height = 20;
        text = @{"N / A", "N / A"};
      }
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Created") {
        profile = GuiBlueTextProfile;
        x = 4;
        y = 36;
        height = 20;
        text = "Created:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileOpen_Created_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        y = 36;
        height = 20;
        text = "N / A";
      }
    }
    new GuiScrollCtrl("GuiScroll_TileEditor_FileOpen_Browse") {
      profile = GuiBlueScrollProfile;
      x = 6;
      y = 112;
      width = 304 - x * 2;
      height = 160;
      hScrollBar = "dynamic";
      vScrollBar = "dynamic";
      new GuiTreeViewCtrl("GuiTreeView_TileEditor_FileOpen") {
        profile = GuiBlueTreeViewProfile;
        width = 132;
        fitparentwidth = true;
      }
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileOpen_Delete") {
      profile = GuiBlueButtonProfile;
      x = 6;
      y = 273;
      width = 70;
      height = 24;
      text = "Delete";
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileOpen_Open") {
      profile = GuiBlueButtonProfile;
      x = 98;
      y = 273;
      width = 100;
      height = 24;
      text = "Open";
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileOpen_Cancel") {
      profile = GuiBlueButtonProfile;
      x = 198;
      y = 273;
      width = 100;
      height = 24;
      text = "Cancel";
    }
  }
  // once the gui is created, let's request a list of
  // clipboard files which the player can use
  triggerserver("gui", this.name, RPL.LISTCLIPBOARDFOLDER);
}
 
function showFileSaveGui() {
  // if the Open or Save window already exists
  // destroy it to be replaced
  if (GuiWindow_TileEditor_FileOpen != NULL)
    GuiWindow_TileEditor_FileOpen.destroy();
  if (GuiWindow_TileEditor_FileSave != NULL)
    GuiWindow_TileEditor_FileSave.destroy();
  new GuiWindowCtrl("GuiWindow_TileEditor_FileSave") {
    profile = GuiBlueWindowProfile;
    destroyonhide = true;
    canresize =
    canminimize = 
    canmaximize = false;
    width = 304;
    height = 304;
    x = screenwidth / 2 - width / 2;
    y = screenheight / 2 - height / 2;
    text = "Save";
    new GuiScrollCtrl("GuiScroll_TileEditor_FileSave_Preview_Image") {
      x = 6;
      y = 24;
      profile = GuiBlueScrollProfile;
      width = 88;
      height = 88;
      hScrollBar = "alwaysOff";
      vScrollBar = "alwaysOff";
      new GuiStretchCtrl("GuiStretch_TileEditor_FileSave") {
        width = 84;
        height = 84;
        new GuiDrawingPanel("GuiDrawingPanel_TileEditor_FileSave_Preview") {
          clearall();
        }
      }
    }
    new GuiScrollCtrl("GuiScroll_TileEditor_FileSave_Preview_Text") {
      profile = GuiBlueScrollProfile;
      x = 92;
      y = 24;
      width = 304 - x - 6;
      height = 88;
      hScrollBar = "dynamic";
      vScrollBar = "dynamic";
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Title") {
        profile = GuiBlueTextProfile;
        x = 4;
        height = 20;
        text = "Title:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Title_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        height = 20;
        text = "N / A";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Extent") {
        profile = GuiBlueTextProfile;
        x = 4;
        y = 18;
        height = 20;
        text = "Extent:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Extent_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        y = 18;
        height = 20;
        text = @{"N / A", "N / A"};
      }
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Created") {
        profile = GuiBlueTextProfile;
        x = 4;
        y = 36;
        height = 20;
        text = "Created:";
      }
      new GuiTextCtrl("GuiText_TileEditor_FileSave_Created_Value") {
        profile = GuiBlueTextProfile;
        x = 56;
        y = 36;
        height = 20;
        text = "N / A";
      }
    }
    new GuiScrollCtrl("GuiScroll_TileEditor_FileSave_Browse") {
      profile = GuiBlueScrollProfile;
      x = 6;
      y = 112;
      width = 304 - x * 2;
      height = 141;
      hScrollBar = "dynamic";
      vScrollBar = "dynamic";
      new GuiTreeViewCtrl("GuiTreeView_TileEditor_FileSave") {
        profile = GuiBlueTreeViewProfile;
        width = 132;
        fitparentwidth = true;
      }
    }
    new GuiTextEditCtrl("GuiTextEdit_TileEditor_FileSave_FilePath") {
      profile = GuiBlueTextEditProfile;
      x = 6;
      y = 253;
      width = 304 - x * 2;
      height = 20;
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileSave_Delete") {
      profile = GuiBlueButtonProfile;
      x = 6;
      y = 273;
      width = 70;
      height = 24;
      text = "Delete";
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileSave_Save") {
      profile = GuiBlueButtonProfile;
      x = 98;
      y = 273;
      width = 100;
      height = 24;
      text = "Save";
    }
    new GuiButtonCtrl("GuiButton_TileEditor_FileSave_Cancel") {
      profile = GuiBlueButtonProfile;
      x = 198;
      y = 273;
      width = 100;
      height = 24;
      text = "Cancel";
    }
  }
  // once the gui is created, let's request a list of
  // clipboard files which the player can use
  triggerserver("gui", this.name, RPL.LISTCLIPBOARDFOLDER);
}
 
function setClipboardFromLevel() {
  // set the clipboard to data from the level selection
 
  if (GuiWindow_TileEditor.tileSelectStart[0] <= GuiWindow_TileEditor.tileSelectEnd[0]) {
    temp.tsx = GuiWindow_TileEditor.tileSelectStart[0];
    temp.tex = GuiWindow_TileEditor.tileSelectEnd[0] + 1;
  } else {
    temp.tsx = GuiWindow_TileEditor.tileSelectEnd[0];
    temp.tex = GuiWindow_TileEditor.tileSelectStart[0] + 1;
  }
  if (GuiWindow_TileEditor.tileSelectStart[1] <= GuiWindow_TileEditor.tileSelectEnd[1]) {
    temp.tsy = GuiWindow_TileEditor.tileSelectStart[1];
    temp.tey = GuiWindow_TileEditor.tileSelectEnd[1] + 1;
  } else {
    temp.tsy = GuiWindow_TileEditor.tileSelectEnd[1];
    temp.tey = GuiWindow_TileEditor.tileSelectStart[1] + 1;
  }
 
  GuiWindow_TileEditor.clipboard = {};
  for (temp.y = temp.tsy; temp.y < temp.tey; temp.y++) {
    temp.arr = {};
    for (temp.x = temp.tsx; temp.x < temp.tex; temp.x++) {
      if (GuiWindow_TileEditor.editingLayer == 0)
        temp.tile = tiles[temp.x, temp.y];
      else 
        temp.tile = tilelayers[GuiWindow_TileEditor.editingLayer].tiles[temp.x, temp.y];
      temp.arr.add(temp.tile);
    }
    GuiWindow_TileEditor.clipboard.add(temp.arr);
  }
 
  drawClipboardPreview();
}
 
function setClipboardFromGui() {
  // set the clipboard to data from the gui selection
 
  if (GuiWindow_TileEditor.tileSelectStart[0] < 0)
    GuiWindow_TileEditor.tileSelectStart[0] = 0;
  elseif(GuiWindow_TileEditor.tileSelectStart[0] >= GuiDrawingPanel_TileEditor.width)
    GuiWindow_TileEditor.tileSelectStart[0] = GuiDrawingPanel_TileEditor.width - 16;
  if (GuiWindow_TileEditor.tileSelectStart[1] < 0)
    GuiWindow_TileEditor.tileSelectStart[1] = 0;
  elseif(GuiWindow_TileEditor.tileSelectStart[1] >= GuiDrawingPanel_TileEditor.height)
    GuiWindow_TileEditor.tileSelectStart[1] = GuiDrawingPanel_TileEditor.height - 16;
  if (GuiWindow_TileEditor.tileSelectEnd[0] < 0)
    GuiWindow_TileEditor.tileSelectEnd[0] = 0;
  elseif(GuiWindow_TileEditor.tileSelectEnd[0] >= GuiDrawingPanel_TileEditor.width)
    GuiWindow_TileEditor.tileSelectEnd[0] = GuiDrawingPanel_TileEditor.width - 16;
  if (GuiWindow_TileEditor.tileSelectEnd[1] < 0)
    GuiWindow_TileEditor.tileSelectEnd[1] = 0;
  elseif(GuiWindow_TileEditor.tileSelectEnd[1] >= GuiDrawingPanel_TileEditor.height)
    GuiWindow_TileEditor.tileSelectEnd[1] = GuiDrawingPanel_TileEditor.height - 16;
 
  if (GuiWindow_TileEditor.tileSelectStart[0] <= GuiWindow_TileEditor.tileSelectEnd[0]) {
    temp.tsx = GuiWindow_TileEditor.tileSelectStart[0];
    temp.tex = GuiWindow_TileEditor.tileSelectEnd[0] + 16;
  } else {
    temp.tsx = GuiWindow_TileEditor.tileSelectEnd[0];
    temp.tex = GuiWindow_TileEditor.tileSelectStart[0] + 16;
  }
  if (GuiWindow_TileEditor.tileSelectStart[1] <= GuiWindow_TileEditor.tileSelectEnd[1]) {
    temp.tsy = GuiWindow_TileEditor.tileSelectStart[1];
    temp.tey = GuiWindow_TileEditor.tileSelectEnd[1] + 16;
  } else {
    temp.tsy = GuiWindow_TileEditor.tileSelectEnd[1];
    temp.tey = GuiWindow_TileEditor.tileSelectStart[1] + 16;
  }
 
  GuiWindow_TileEditor.clipboard = {};
  for (temp.y = temp.tsy; temp.y < temp.tey; temp.y += 16) {
    temp.arr = {};
    for (temp.x = temp.tsx; temp.x < temp.tex; temp.x += 16)
      temp.arr.add(getXYTile(temp.x, temp.y));
    GuiWindow_TileEditor.clipboard.add(temp.arr);
  }
 
  drawClipboardPreview();
}
 
function drawClipboardPreview() {
  // draw the clipboard preview gui which follows the mouse
 
  // if it already exists, destroy it to be replaced
  if (GuiShowImg_TileEditor_ClipboardPreview != NULL)
    GuiShowImg_TileEditor_ClipboardPreview.destroy();
  new GuiShowImgCtrl("GuiShowImg_TileEditor_ClipboardPreview") {
    useownprofile = true;
    profile.cankeyfocus =
    profile.modal = false;
    x = screenx(int(mousex), 0) - 2;
    y = screeny(0, int(mousey)) - 2;
    width = GuiWindow_TileEditor.clipboard[0].size() * 16 + 4;
    height = GuiWindow_TileEditor.clipboard.size() * 16 + 4;
    polygon = {
      0, 0,
      width, 0,
      width, height,
      0, height
    };
    red =
    green = 
    blue = 0.0;
    alpha = 0.6;
    new GuiDrawingPanel("GuiDrawingPanel_TileEditor_ClipboardPreview") {
      useownprofile = true;
      profile.cankeyfocus =
      profile.modal = false;
      x =
      y = 2;
      width = GuiWindow_TileEditor.clipboard[0].size() * 16;
      height = GuiWindow_TileEditor.clipboard.size() * 16;
      clearall();
 
      // draw the tiles to the preview based on
      // the currently selected tileset image
      for (temp.y = 0; temp.y < GuiWindow_TileEditor.clipboard.size(); temp.y++) {
        for (temp.x = 0; temp.x < GuiWindow_TileEditor.clipboard[0].size(); temp.x++) {
          temp.xy = getTileXY(GuiWindow_TileEditor.clipboard[temp.y][temp.x]);
          drawimagerectangle(temp.x * 16, temp.y * 16, GuiWindow_TileEditor.tilesetImage, temp.xy[0], temp.xy[1], 16, 16);
        }
      }
      flickering = true;
      alpha = 0.5;
    }
  }
  // push the preview gui behind all gui's inside of GraalControl
  GuiShowImg_TileEditor_ClipboardPreview.pushtoback();
}
 
function getFloodedAreaZero(temp.x, temp.y) {
  // the flood fill algorithm for tiles on layer zero
  // it uses a non-recursive, stack based approach
 
  temp.tile = tiles[temp.x, temp.y];
  temp.a = {temp.x, temp.y, temp.x, temp.y};
  temp.q = {{temp.x, temp.y}};
  temp.tileMap = new [height][width];
  maxlooplimit = 100000;
  temp.minX = temp.x - 64;
  if (temp.minX < 0)
    temp.minX = 0;
  temp.minY = temp.y - 64;
  if (temp.minY < 0)
    temp.minY = 0;
  temp.maxX = temp.x + 64;
  if (temp.maxX >= width)
    temp.maxX = width - 1;
  temp.maxY = temp.y + 64;
  if (temp.maxY >= height)
    temp.maxY = height - 1;
  while (temp.q.size() != 0) {
    temp.n = temp.q[0];
    temp.q.delete(0);
    temp.x = temp.n[0];
    temp.y = temp.n[1];
    if (tiles[temp.x, temp.y] == temp.tile
     && !temp.tileMap[temp.y][temp.x]) {
      temp.tileMap[temp.y][temp.x] = true;
      temp.a.add(temp.n);
      if (temp.a.size() == 4100)
        break;
      if (temp.x < temp.a[0])
        temp.a[0] = temp.x;
      elseif (temp.x > temp.a[2])
        temp.a[2] = temp.x;
      if (temp.y < temp.a[1])
        temp.a[1] = temp.y;
      elseif (temp.y > temp.a[3])
        temp.a[3] = temp.y;
      if (temp.y > temp.minY)
        temp.q.add({temp.x, temp.y - 1});
      if (temp.x > temp.minX)
        temp.q.add({temp.x - 1, temp.y});
      if (temp.y < temp.maxY)
        temp.q.add({temp.x, temp.y + 1});
      if (temp.x < temp.maxX)
        temp.q.add({temp.x + 1, temp.y});
    }
  }
  return temp.a;
}
 
function getFloodedAreaLayer(temp.tileLayer, temp.x, temp.y) {
  // the flood fill algorithm for tiles on layers 1+
  // it uses a non-recursive, stack based approach
 
  temp.tile = temp.tileLayer.tiles[temp.x, temp.y];
  temp.a = {temp.x, temp.y, temp.x, temp.y};
  temp.q = {{temp.x, temp.y}};
  temp.tileMap = new [height][width];
  maxlooplimit = 100000;
  temp.minX = temp.x - 64;
  if (temp.minX < 0)
    temp.minX = 0;
  temp.minY = temp.y - 64;
  if (temp.minY < 0)
    temp.minY = 0;
  temp.maxX = temp.x + 64;
  if (temp.maxX >= width)
    temp.maxX = width - 1;
  temp.maxY = temp.y + 64;
  if (temp.maxY >= height)
    temp.maxY = height - 1;
  while (temp.q.size() != 0) {
    temp.n = temp.q[0];
    temp.q.delete(0);
    temp.x = temp.n[0];
    temp.y = temp.n[1];
    if (temp.tileLayer.tiles[temp.x, temp.y] == temp.tile
     && !temp.tileMap[temp.y][temp.x]) {
      temp.tileMap[temp.y][temp.x] = true;
      temp.a.add(temp.n);
      if (temp.a.size() == 4100)
        break;
      if (temp.x < temp.a[0])
        temp.a[0] = temp.x;
      elseif (temp.x > temp.a[2])
        temp.a[2] = temp.x;
      if (temp.y < temp.a[1])
        temp.a[1] = temp.y;
      elseif (temp.y > temp.a[3])
        temp.a[3] = temp.y;
      if (temp.y > temp.minY)
        temp.q.add({temp.x, temp.y - 1});
      if (temp.x > temp.minX)
        temp.q.add({temp.x - 1, temp.y});
      if (temp.y < temp.maxY)
        temp.q.add({temp.x, temp.y + 1});
      if (temp.x < temp.maxX)
        temp.q.add({temp.x + 1, temp.y});
    }
  }
  return temp.a;
}
 
function editorUndo() {
  // if the program isn't open, ignore it
  if (GuiWindow_TileEditor == NULL)
    return;
  // if you've reached the end of history
  // there's nothing to undo
  if (GuiWindow_TileEditor.historyIndex == GuiWindow_TileEditor.history.size()) {
    shared.adminmessage("nothing to undo");
    return;
  }
 
  // send a standard paste request to the server
  // providing the data from the history
  triggerserver(
    "gui", 
    this.name, 
    RPL.PLACETILES, 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][3], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][0], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][1], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][2]
  );
 
  // increase stack position
  GuiWindow_TileEditor.historyIndex++;
}
 
function editorRedo() {
  // if the program is closed, ignore
  if (GuiWindow_TileEditor == NULL)
    return;
  // if you've reached the beginning of history
  // there is nothing to redo
  if (GuiWindow_TileEditor.historyIndex == 0) {
    shared.adminmessage("nothing to redo");
    return;
  }
 
  // decrease the stack position
  GuiWindow_TileEditor.historyIndex--;
 
  // send a standard paste request to the server
  // providing the data from the history
  triggerserver(
    "gui", 
    this.name, 
    RPL.PLACETILES, 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][4], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][0], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][1], 
    GuiWindow_TileEditor.history[GuiWindow_TileEditor.historyIndex][2]
  );
}
 
function updateFadeEffect() {
  // enables or disables the fading effect of
  // all inactive tilelayers
 
  if (GuiWindow_TileEditor.fadeLayers) {
    for (temp.i = 0; temp.i < tilelayercount; temp.i++) {
      if (temp.i == GuiWindow_TileEditor.editingLayer) {
        tilelayers[temp.i].red = 
        tilelayers[temp.i].green = 
        tilelayers[temp.i].blue =
        tilelayers[temp.i].alpha = 1.0;
      } else {
        tilelayers[temp.i].red = 
        tilelayers[temp.i].green = 
        tilelayers[temp.i].blue =  0.25;
      if (temp.i != 0)
        tilelayers[temp.i].alpha = 0.25;
      }
    }
  } else {
    for (temp.i = 0; temp.i < tilelayercount; temp.i++) {
      tilelayers[temp.i].red = 
      tilelayers[temp.i].green = 
      tilelayers[temp.i].blue =
      tilelayers[temp.i].alpha = 1.0;
    }
  }
}
 
function updateGridEffect() {
  // enables or disables the grid effect
 
  if (this.gridImageCount != NULL) {
    hideimgs(300, 300 + this.gridImageCount);
    this.gridImageCount = NULL;
  }
  if (GuiWindow_TileEditor.grid) {
    temp.w = width;
    temp.h = height;
    for (temp.y = 0; temp.y < temp.h; temp.y++) {
      with (findimg(300 + temp.i++)) {
        layer = 3;
        if (temp.y % 64 == 0) {
          red = 1.0;
          green = 0.0;
          blue = 0.0;
          polygon = {
            0, temp.y,
            temp.w, temp.y,
            temp.w, temp.y + 1/4,
            0, temp.y + 1/4
          };
        } elseif (temp.y % 32 == 0) {
          red = 0.0;
          green = 1.0;
          blue = 0.0;
          polygon = {
            0, temp.y,
            temp.w, temp.y,
            temp.w, temp.y + 3/16,
            0, temp.y + 3/16
          };
        } elseif (temp.y % 16 == 0) {
          red = 1.0;
          green = 0.0;
          blue = 1.0;
          polygon = {
            0, temp.y,
            temp.w, temp.y,
            temp.w, temp.y + 1/8,
            0, temp.y + 1/8
          };
        } elseif (temp.y % 8 == 0) {
          red = 0.0;
          green = 1.0;
          blue = 1.0;
          polygon = {
            0, temp.y,
            temp.w, temp.y,
            temp.w, temp.y + 1/16,
            0, temp.y + 1/16
          };
        } else {
          polygon = {
            0, temp.y,
            temp.w, temp.y,
            temp.w, temp.y + 1/16,
            0, temp.y + 1/16
          };
        }
        alpha = 0.5;
      }
    }
    for (temp.x = 0; temp.x < temp.w; temp.x++) {
      with (findimg(300 + temp.i++)) {
        layer = 3;
        if (temp.x % 64 == 0) {
          red = 1.0;
          green = 0.0;
          blue = 0.0;
          polygon = {
            temp.x, 0,
            temp.x, temp.h,
            temp.x + 1/4, temp.h,
            temp.x + 1/4, 0
          };
        } elseif (temp.x % 32 == 0) {
          red = 0.0;
          green = 1.0;
          blue = 0.0;
          polygon = {
            temp.x, 0,
            temp.x, temp.h,
            temp.x + 3/16, temp.h,
            temp.x + 3/16, 0
          };
        } elseif (temp.x % 16 == 0) {
          red = 1.0;
          green = 0.0;
          blue = 1.0;
          polygon = {
            temp.x, 0,
            temp.x, temp.h,
            temp.x + 1/8, temp.h,
            temp.x + 1/8, 0
          };
        } elseif (temp.x % 8 == 0) {
          red = 0.0;
          green = 1.0;
          blue = 1.0;
          polygon = {
            temp.x, 0,
            temp.x, temp.h,
            temp.x + 1/16, temp.h,
            temp.x + 1/16, 0
          };
        } else {
          polygon = {
            temp.x, 0,
            temp.x, temp.h,
            temp.x + 1/16, temp.h,
            temp.x + 1/16, 0
          };
        }
        alpha = 0.5;
      }
    }
    this.gridImageCount = temp.i;
  }
}
 
function getTileXY(temp.tile) {
  // returns the position of the tile on the tileset image
  // useful for rendering tiles on a drawing panel
  return {
    int(temp.tile % 16.0) * 16 + int(temp.tile / 512.0) * 256,
    (int(temp.tile / 16.0) * 16) % 512
  };
}
 
function getXYTile(temp.x, temp.y) {
  // returns the tile from the position at the tileset image
  // useful for getting tile data from a gui selection
  return int(temp.x / 16.0) % 16 + int(temp.x / 256.0) * 512 + int(temp.y / 16.0) * 16 % 512;
}
 
/**
 * the following are provided for public script interfacing
 * in an effort to make it easier to integrate this tool
 * into a staff tool manager
 */
 
public function close() {
  // asynchronous close operation
  trigger("onCloseEditor");
}
 
public function open() {
  // asynchronous open request operation
  trigger("onRequestUse");
}
 
public function isOpen() {
  // returns a boolean telling if the program
  // is open or not
  return (GuiWindow_TileEditor != NULL);
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
  • 741
  • 742
  • 743
  • 744
  • 745
  • 746
  • 747
  • 748
  • 749
  • 750
  • 751
  • 752
  • 753
  • 754
  • 755
  • 756
  • 757
  • 758
  • 759
  • 760
  • 761
  • 762
  • 763
  • 764
  • 765
  • 766
  • 767
  • 768
  • 769
  • 770
  • 771
  • 772
  • 773
  • 774
  • 775
  • 776
  • 777
  • 778
  • 779
  • 780
  • 781
  • 782
  • 783
  • 784
  • 785
  • 786
  • 787
  • 788
  • 789
  • 790
  • 791
  • 792
  • 793
  • 794
  • 795
  • 796
  • 797
  • 798
  • 799
  • 800
  • 801
  • 802
  • 803
  • 804
  • 805
  • 806
  • 807
  • 808
  • 809
  • 810
  • 811
  • 812
  • 813
  • 814
  • 815
  • 816
  • 817
  • 818
  • 819
  • 820
  • 821
  • 822
  • 823
  • 824
  • 825
  • 826
  • 827
  • 828
  • 829
  • 830
  • 831
  • 832
  • 833
  • 834
  • 835
  • 836
  • 837
  • 838
  • 839
  • 840
  • 841
  • 842
  • 843
  • 844
  • 845
  • 846
  • 847
  • 848
  • 849
  • 850
  • 851
  • 852
  • 853
  • 854
  • 855
  • 856
  • 857
  • 858
  • 859
  • 860
  • 861
  • 862
  • 863
  • 864
  • 865
  • 866
  • 867
  • 868
  • 869
  • 870
  • 871
  • 872
  • 873
  • 874
  • 875
  • 876
  • 877
  • 878
  • 879
  • 880
  • 881
  • 882
  • 883
  • 884
  • 885
  • 886
  • 887
  • 888
  • 889
  • 890
  • 891
  • 892
  • 893
  • 894
  • 895
  • 896
  • 897
  • 898
  • 899
  • 900
  • 901
  • 902
  • 903
  • 904
  • 905
  • 906
  • 907
  • 908
  • 909
  • 910
  • 911
  • 912
  • 913
  • 914
  • 915
  • 916
  • 917
  • 918
  • 919
  • 920
  • 921
  • 922
  • 923
  • 924
  • 925
  • 926
  • 927
  • 928
  • 929
  • 930
  • 931
  • 932
  • 933
  • 934
  • 935
  • 936
  • 937
  • 938
  • 939
  • 940
  • 941
  • 942
  • 943
  • 944
  • 945
  • 946
  • 947
  • 948
  • 949
  • 950
  • 951
  • 952
  • 953
  • 954
  • 955
  • 956
  • 957
  • 958
  • 959
  • 960
  • 961
  • 962
  • 963
  • 964
  • 965
  • 966
  • 967
  • 968
  • 969
  • 970
  • 971
  • 972
  • 973
  • 974
  • 975
  • 976
  • 977
  • 978
  • 979
  • 980
  • 981
  • 982
  • 983
  • 984
  • 985
  • 986
  • 987
  • 988
  • 989
  • 990
  • 991
  • 992
  • 993
  • 994
  • 995
  • 996
  • 997
  • 998
  • 999
  • 1000
  • 1001
  • 1002
  • 1003
  • 1004
  • 1005
  • 1006
  • 1007
  • 1008
  • 1009
  • 1010
  • 1011
  • 1012
  • 1013
  • 1014
  • 1015
  • 1016
  • 1017
  • 1018
  • 1019
  • 1020
  • 1021
  • 1022
  • 1023
  • 1024
  • 1025
  • 1026
  • 1027
  • 1028
  • 1029
  • 1030
  • 1031
  • 1032
  • 1033
  • 1034
  • 1035
  • 1036
  • 1037
  • 1038
  • 1039
  • 1040
  • 1041
  • 1042
  • 1043
  • 1044
  • 1045
  • 1046
  • 1047
  • 1048
  • 1049
  • 1050
  • 1051
  • 1052
  • 1053
  • 1054
  • 1055
  • 1056
  • 1057
  • 1058
  • 1059
  • 1060
  • 1061
  • 1062
  • 1063
  • 1064
  • 1065
  • 1066
  • 1067
  • 1068
  • 1069
  • 1070
  • 1071
  • 1072
  • 1073
  • 1074
  • 1075
  • 1076
  • 1077
  • 1078
  • 1079
  • 1080
  • 1081
  • 1082
  • 1083
  • 1084
  • 1085
  • 1086
  • 1087
  • 1088
  • 1089
  • 1090
  • 1091
  • 1092
  • 1093
  • 1094
  • 1095
  • 1096
  • 1097
  • 1098
  • 1099
  • 1100
  • 1101
  • 1102
  • 1103
  • 1104
  • 1105
  • 1106
  • 1107
  • 1108
  • 1109
  • 1110
  • 1111
  • 1112
  • 1113
  • 1114
  • 1115
  • 1116
  • 1117
  • 1118
  • 1119
  • 1120
  • 1121
  • 1122
  • 1123
  • 1124
  • 1125
  • 1126
  • 1127
  • 1128
  • 1129
  • 1130
  • 1131
  • 1132
  • 1133
  • 1134
  • 1135
  • 1136
  • 1137
  • 1138
  • 1139
  • 1140
  • 1141
  • 1142
  • 1143
  • 1144
  • 1145
  • 1146
  • 1147
  • 1148
  • 1149
  • 1150
  • 1151
  • 1152
  • 1153
  • 1154
  • 1155
  • 1156
  • 1157
  • 1158
  • 1159
  • 1160
  • 1161
  • 1162
  • 1163
  • 1164
  • 1165
  • 1166
  • 1167
  • 1168
  • 1169
  • 1170
  • 1171
  • 1172
  • 1173
  • 1174
  • 1175
  • 1176
  • 1177
  • 1178
  • 1179
  • 1180
  • 1181
  • 1182
  • 1183
  • 1184
  • 1185
  • 1186
  • 1187
  • 1188
  • 1189
  • 1190
  • 1191
  • 1192
  • 1193
  • 1194
  • 1195
  • 1196
  • 1197
  • 1198
  • 1199
  • 1200
  • 1201
  • 1202
  • 1203
  • 1204
  • 1205
  • 1206
  • 1207
  • 1208
  • 1209
  • 1210
  • 1211
  • 1212
  • 1213
  • 1214
  • 1215
  • 1216
  • 1217
  • 1218
  • 1219
  • 1220
  • 1221
  • 1222
  • 1223
  • 1224
  • 1225
  • 1226
  • 1227
  • 1228
  • 1229
  • 1230
  • 1231
  • 1232
  • 1233
  • 1234
  • 1235
  • 1236
  • 1237
  • 1238
  • 1239
  • 1240
  • 1241
  • 1242
  • 1243
  • 1244
  • 1245
  • 1246
  • 1247
  • 1248
  • 1249
  • 1250
  • 1251
  • 1252
  • 1253
  • 1254
  • 1255
  • 1256
  • 1257
  • 1258
  • 1259
  • 1260
  • 1261
  • 1262
  • 1263
  • 1264
  • 1265
  • 1266
  • 1267
  • 1268
  • 1269
  • 1270
  • 1271
  • 1272
  • 1273
  • 1274
  • 1275
  • 1276
  • 1277
  • 1278
  • 1279
  • 1280
  • 1281
  • 1282
  • 1283
  • 1284
  • 1285
  • 1286
  • 1287
  • 1288
  • 1289
  • 1290
  • 1291
  • 1292
  • 1293
  • 1294
  • 1295
  • 1296
  • 1297
  • 1298
  • 1299
  • 1300
  • 1301
  • 1302
  • 1303
  • 1304
  • 1305
  • 1306
  • 1307
  • 1308
  • 1309
  • 1310
  • 1311
  • 1312
  • 1313
  • 1314
  • 1315
  • 1316
  • 1317
  • 1318
  • 1319
  • 1320
  • 1321
  • 1322
  • 1323
  • 1324
  • 1325
  • 1326
  • 1327
  • 1328
  • 1329
  • 1330
  • 1331
  • 1332
  • 1333
  • 1334
  • 1335
  • 1336
  • 1337
  • 1338
  • 1339
  • 1340
  • 1341
  • 1342
  • 1343
  • 1344
  • 1345
  • 1346
  • 1347
  • 1348
  • 1349
  • 1350
  • 1351
  • 1352
  • 1353
  • 1354
  • 1355
  • 1356
  • 1357
  • 1358
  • 1359
  • 1360
  • 1361
  • 1362
  • 1363
  • 1364
  • 1365
  • 1366
  • 1367
  • 1368
  • 1369
  • 1370
  • 1371
  • 1372
  • 1373
  • 1374
  • 1375
  • 1376
  • 1377
  • 1378
  • 1379
  • 1380
  • 1381
  • 1382
  • 1383
  • 1384
  • 1385
  • 1386
  • 1387
  • 1388
  • 1389
  • 1390
  • 1391
  • 1392
  • 1393
  • 1394
  • 1395
  • 1396
  • 1397
  • 1398
  • 1399
  • 1400
  • 1401
  • 1402
  • 1403
  • 1404
  • 1405
  • 1406
  • 1407
  • 1408
  • 1409
  • 1410
  • 1411
  • 1412
  • 1413
  • 1414
  • 1415
  • 1416
  • 1417
  • 1418
  • 1419
  • 1420
  • 1421
  • 1422
  • 1423
  • 1424
  • 1425
  • 1426
  • 1427
  • 1428
  • 1429
  • 1430
  • 1431
  • 1432
  • 1433
  • 1434
  • 1435
  • 1436
  • 1437
  • 1438
  • 1439
  • 1440
  • 1441
  • 1442
  • 1443
  • 1444
  • 1445
  • 1446
  • 1447
  • 1448
  • 1449
  • 1450
  • 1451
  • 1452
  • 1453
  • 1454
  • 1455
  • 1456
  • 1457
  • 1458
  • 1459
  • 1460
  • 1461
  • 1462
  • 1463
  • 1464
  • 1465
  • 1466
  • 1467
  • 1468
  • 1469
  • 1470
  • 1471
  • 1472
  • 1473
  • 1474
  • 1475
  • 1476
  • 1477
  • 1478
  • 1479
  • 1480
  • 1481
  • 1482
  • 1483
  • 1484
  • 1485
  • 1486
  • 1487
  • 1488
  • 1489
  • 1490
  • 1491
  • 1492
  • 1493
  • 1494
  • 1495
  • 1496
  • 1497
  • 1498
  • 1499
  • 1500
  • 1501
  • 1502
  • 1503
  • 1504
  • 1505
  • 1506
  • 1507
  • 1508
  • 1509
  • 1510
  • 1511
  • 1512
  • 1513
  • 1514
  • 1515
  • 1516
  • 1517
  • 1518
  • 1519
  • 1520
  • 1521
  • 1522
  • 1523
  • 1524
  • 1525
  • 1526
  • 1527
  • 1528
  • 1529
  • 1530
  • 1531
  • 1532
  • 1533
  • 1534
  • 1535
  • 1536
  • 1537
  • 1538
  • 1539
  • 1540
  • 1541
  • 1542
  • 1543
  • 1544
  • 1545
  • 1546
  • 1547
  • 1548
  • 1549
  • 1550
  • 1551
  • 1552
  • 1553
  • 1554
  • 1555
  • 1556
  • 1557
  • 1558
  • 1559
  • 1560
  • 1561
  • 1562
  • 1563
  • 1564
  • 1565
  • 1566
  • 1567
  • 1568
  • 1569
  • 1570
  • 1571
  • 1572
  • 1573
  • 1574
  • 1575
  • 1576
  • 1577
  • 1578
  • 1579
  • 1580
  • 1581
  • 1582
  • 1583
  • 1584
  • 1585
  • 1586
  • 1587
  • 1588
  • 1589
  • 1590
  • 1591
  • 1592
  • 1593
  • 1594
  • 1595
  • 1596
  • 1597
  • 1598
  • 1599
  • 1600
  • 1601
  • 1602
  • 1603
  • 1604
  • 1605
  • 1606
  • 1607
  • 1608
  • 1609
  • 1610
  • 1611
  • 1612
  • 1613
  • 1614
  • 1615
  • 1616
  • 1617
  • 1618
  • 1619
  • 1620
  • 1621
  • 1622
  • 1623
  • 1624
  • 1625
  • 1626
  • 1627
  • 1628
  • 1629
  • 1630
  • 1631
  • 1632
  • 1633
  • 1634
  • 1635
  • 1636
  • 1637
  • 1638
  • 1639
  • 1640
  • 1641
  • 1642
  • 1643
  • 1644
  • 1645
  • 1646
  • 1647
  • 1648
  • 1649
  • 1650
  • 1651
  • 1652
  • 1653
  • 1654
  • 1655
  • 1656
  • 1657
  • 1658
  • 1659
  • 1660
  • 1661
  • 1662
  • 1663
  • 1664
  • 1665
  • 1666
  • 1667
  • 1668
  • 1669
  • 1670
  • 1671
  • 1672
  • 1673
  • 1674
  • 1675
  • 1676
  • 1677
  • 1678
  • 1679
  • 1680
  • 1681
  • 1682
  • 1683
  • 1684
  • 1685
  • 1686
  • 1687
  • 1688
  • 1689
  • 1690
  • 1691
  • 1692
  • 1693
  • 1694
  • 1695
  • 1696
  • 1697
  • 1698
  • 1699
  • 1700
  • 1701
  • 1702
  • 1703
  • 1704
  • 1705
  • 1706
  • 1707
  • 1708
  • 1709
  • 1710
  • 1711
  • 1712
  • 1713
  • 1714
  • 1715
  • 1716
  • 1717
  • 1718
  • 1719
  • 1720
  • 1721
  • 1722
  • 1723
  • 1724
  • 1725
  • 1726
  • 1727
  • 1728
  • 1729
  • 1730
  • 1731
  • 1732
  • 1733
  • 1734
  • 1735
  • 1736
  • 1737
  • 1738
  • 1739
  • 1740
  • 1741
  • 1742
  • 1743
  • 1744
  • 1745
  • 1746
  • 1747
  • 1748
  • 1749
  • 1750
  • 1751
  • 1752
  • 1753
  • 1754
  • 1755
  • 1756
  • 1757
  • 1758
  • 1759
  • 1760
  • 1761
  • 1762
  • 1763
  • 1764
  • 1765
  • 1766
  • 1767
  • 1768
  • 1769
  • 1770
  • 1771
  • 1772
  • 1773
  • 1774
  • 1775
  • 1776
  • 1777
  • 1778
  • 1779
  • 1780
  • 1781
  • 1782
  • 1783
  • 1784
  • 1785
  • 1786
  • 1787
  • 1788
  • 1789
  • 1790
  • 1791
  • 1792
  • 1793
  • 1794
  • 1795
  • 1796
  • 1797
  • 1798
  • 1799
  • 1800
  • 1801
  • 1802
  • 1803
  • 1804
  • 1805
  • 1806
  • 1807
  • 1808
  • 1809
  • 1810
  • 1811
  • 1812
  • 1813
  • 1814
  • 1815
  • 1816
  • 1817
  • 1818
  • 1819
  • 1820
  • 1821
  • 1822
  • 1823
  • 1824
  • 1825
  • 1826
  • 1827
  • 1828
  • 1829
  • 1830
  • 1831
  • 1832
  • 1833
  • 1834
  • 1835
  • 1836
  • 1837
  • 1838
  • 1839
  • 1840
  • 1841
  • 1842
  • 1843
  • 1844
  • 1845
  • 1846
  • 1847
  • 1848
  • 1849
  • 1850
  • 1851
  • 1852
  • 1853
  • 1854
  • 1855
  • 1856
  • 1857
  • 1858
  • 1859
  • 1860
  • 1861
  • 1862
  • 1863
  • 1864
  • 1865
  • 1866
  • 1867
  • 1868
  • 1869
  • 1870
  • 1871
  • 1872
  • 1873
  • 1874
  • 1875
  • 1876
  • 1877
  • 1878
  • 1879
  • 1880
  • 1881
  • 1882
  • 1883
  • 1884
  • 1885
  • 1886
  • 1887
  • 1888
  • 1889
  • 1890
  • 1891
  • 1892
  • 1893
  • 1894
  • 1895
  • 1896
  • 1897
  • 1898
  • 1899
  • 1900
  • 1901
  • 1902
  • 1903
  • 1904
  • 1905
  • 1906
  • 1907
  • 1908
  • 1909
  • 1910
  • 1911
  • 1912
  • 1913
  • 1914
  • 1915
  • 1916
  • 1917
  • 1918
  • 1919
  • 1920
  • 1921
  • 1922
  • 1923
  • 1924
  • 1925
  • 1926
  • 1927
  • 1928
  • 1929
  • 1930
  • 1931
  • 1932
  • 1933
  • 1934
  • 1935
  • 1936
  • 1937
  • 1938
  • 1939
  • 1940
  • 1941
  • 1942
  • 1943
  • 1944
  • 1945
  • 1946
  • 1947
  • 1948
  • 1949
  • 1950
  • 1951
  • 1952
  • 1953
  • 1954
  • 1955
  • 1956
  • 1957
  • 1958
  • 1959
  • 1960
  • 1961
  • 1962
  • 1963
  • 1964
  • 1965
  • 1966
  • 1967
  • 1968
  • 1969
  • 1970
  • 1971
  • 1972
  • 1973
  • 1974
  • 1975
  • 1976
  • 1977
  • 1978
  • 1979
  • 1980
  • 1981
  • 1982
  • 1983
  • 1984
  • 1985
  • 1986
  • 1987
  • 1988
  • 1989
  • 1990
  • 1991
  • 1992
  • 1993
  • 1994
  • 1995
  • 1996
  • 1997
  • 1998
  • 1999
  • 2000
  • 2001
  • 2002
  • 2003
  • 2004
  • 2005
  • 2006
  • 2007
  • 2008
  • 2009
  • 2010
  • 2011
  • 2012
  • 2013
  • 2014
  • 2015
  • 2016
  • 2017
  • 2018
  • 2019
  • 2020
  • 2021
  • 2022
  • 2023
  • 2024
  • 2025
  • 2026
  • 2027
  • 2028
  • 2029
  • 2030
  • 2031
  • 2032
  • 2033
  • 2034
  • 2035
  • 2036
  • 2037
  • 2038
  • 2039
  • 2040
  • 2041
  • 2042
  • 2043
  • 2044
  • 2045
  • 2046
  • 2047
  • 2048
  • 2049
  • 2050
  • 2051
  • 2052
  • 2053
  • 2054
  • 2055
  • 2056
  • 2057
  • 2058
  • 2059
  • 2060
  • 2061
  • 2062
  • 2063
  • 2064
  • 2065
  • 2066
  • 2067
  • 2068
  • 2069
  • 2070
  • 2071
  • 2072
  • 2073
  • 2074
  • 2075
  • 2076
  • 2077
  • 2078
  • 2079
  • 2080
  • 2081
  • 2082
  • 2083
  • 2084
  • 2085
  • 2086
  • 2087
  • 2088
  • 2089
  • 2090
  • 2091
  • 2092
  • 2093
  • 2094
  • 2095
  • 2096
  • 2097
  • 2098
  • 2099
  • 2100
  • 2101
  • 2102
  • 2103
  • 2104
  • 2105
  • 2106
  • 2107
  • 2108
  • 2109
  • 2110
  • 2111
  • 2112
  • 2113
  • 2114
  • 2115
  • 2116
  • 2117
  • 2118
  • 2119
  • 2120
  • 2121
  • 2122
  • 2123
  • 2124
  • 2125
  • 2126
  • 2127
  • 2128
  • 2129
  • 2130
  • 2131
  • 2132
  • 2133
  • 2134
  • 2135
  • 2136
  • 2137
  • 2138
  • 2139
  • 2140
  • 2141
  • 2142
  • 2143
  • 2144
  • 2145
  • 2146
  • 2147