[Download] 1.2 Mapping Client
#1
What is this?

This is a modified Assault Cube executable with various new features and quality-of-life improvements that dramatically improve workflow with the in-game map editor. Since it uses your existing Assault Cube resources, it is a small download and doesn’t overwrite any files.

Editor Interface Improvements

Cube Grid

"Solid" cubes now have purple grid guides to differentiate them from non-solid cubes

The cube grid and selection edges now fade out when the mouse is idle for a few moments (moving the mouse will bring back the grid)

[Image: autohidegrid.gif]

Entities

Entities can now be selected and edited from any distance even if other entities are closer to the player.

Entity sparkles are now colorized according to the corresponding entity so identifying them from a distance is easier (arrows from playerstart entities also colorized)

The closest/selected entity now radiates smoke particles while retaining its sparkle color.

The entity information text that is visible on the bottom-left of the screen has been reformatted into three lines: the first shows the selection status of entities, the second is the standard entity info (entity name is now colorized to match the entity sparkle), and the third line displays the file name/location of the resource on disk for sounds and mapmodels.

[Image: sparkles.gif]

There is now a toggleable “clean edit” mode for the editor that hides entity particles and reduces the size of the entity information text (does not hide grid or selection edges as they fade out automatically when the mouse is idle)

[Image: cleaneditmode.gif]

Mapmodels

The rotation of mapmodels is now displayed in degrees and editing using “domodifier” (holding the [1] key and scrolling the mouse wheel) nows cycles through the 24 unique angles possible.

[Image: mapmodelselection.gif]

New Cubescript Commands

Documentation for all commands available later in this post (Must be manually installed for in-game reference/tab autocomplete to work)

Interface

/cleanedit - enables (1) or disables (0) clean edit mode.

/togglecleanedit - toggles clean edit mode (recommended bind)

/lockselent - locks (1) or unlocks (0) the entity selection to the nearest/selected entity. Selected entities may be edited from any distance even if other entities are closer to the player.

/togglelockselent - toggles entity selection lock (recommended bind)

Cube Selection

/savesel - stores selection info in memory

/reselect - restores selection(s) from memory

[Image: saveselreselect.gif]

/select_all - selects entire map (new maps must be saved/loaded for this command to work)

/undo_sel - removes the most recent selection(s) Useful when you make complex selections and accidentally make a stray selection of additional cubes

Cube Information

Commands that return a value can be passed to other cubescript commands, for example to the /echo command to print the value to the local player or the new selection filtering command /filter_sel

/texname - returns the filename of the most recently selected cube’s wall/floor/ceiling texture

/curheight - prints all height info of the most recently selected cube or a cube at a given location

/getcurheight - returns specific height info for a cube

/curtex - prints all texture slot information of the most recently selected cube or a cube at a given location

/getcurtex - returns specific texture info for a cube

[Image: curtex.png]

Miscellaneous

/equalize_sels - A variation of the existing /equalize command that levels the floor/ceiling of all selections, except this one levels all selections to the same height instead of each selection being calculated separately

/paste_half - pastes only floor cubes or only ceiling cubes from copied selection. The choice of floor or ceiling is derived from which the player is looking at

[Image: paste-half.gif]

/scalelight - A variation of the existing command /scalelights except this scales an individual light’s size and/or intensity

[Image: scalelight.gif]

/clearrecenttex - clears the history of recent textures used in the current map

/tex2front - brings textures from a selected cube to the front of the texture history list for immediate application

Selection Filtering

Possibly the most powerful new feature is the ability to process cube selections based on various parameters such as a cube’s texture choice or height, or combine/subtract overlapping selections.

[Image: filter-sel-demo.gif]

The command format is /filter_sel [T] [V] where T is the type of filter to apply and V is up to seven values based on the filter type used. Typing /filter_sel in the console (with the documentation installed) will show the type options which I will also list below:

Filter by Cube Texture

FLOOR_T, WALL_T, UPWALL_T, CEIL_T, where V is up to seven slot numbers to match

Filter by Cube Height

FLOOR_H, CEIL_H, where V is up to seven height values to match

Filter by Cube Type

SOLID, HEIGHTFIELD, CORNER, SPACE, where (V > 0) is matching the type, and (V <= 0) excluding the type. “SPACE” cubes are simply normal cubes not of the other types.

Miscellaneous

DISSOLVE, where V is the percent of cubes to randomly remove from the selection(s)

SUBTRACT, where the V most recent selection(s) are subtracted from the remaining selection(s)

MERGE, where V is unused and all selections are combined/condensed into non-overlapping selections

Installation

Client Download

Windows - 1.2MB

Extract to the same location as your existing Assault Cube installation. Run mapper.bat to launch.

Configuration

Optional, but I highly recommend binding the commands /togglecleanedit and /togglelockselent to improve editing efficiency. I use F3 and F4, respectively.

You can set these binds in game by typing the following:

/editbind F3 togglecleanedit

/editbind F4 togglelockselent

Documentation

For the in-game console’s TAB-autocomplete function to work with the new commands and the command reference to appear you will want to manually install this documentation.

Add the following code to your config/docs.cfg file somewhere (like the top)

docsection [New]

//----------------------------------------------------------------------------

docident [scalelight] [Scales nearest/selected light];

docargument [S] [Size change (percentage)] [] [0];

docargument [I] [Intensity change (percentage)] [] [0];

//----------------------------------------------------------------------------

docident [togglelockselent] [toggles isolated selection of an entity, where the entity is selected as the closest entity regardless of any closer entities.];

docremark [The desired entity must be the closest entity when making selection.];

//----------------------------------------------------------------------------

docident [filter_sel] [filters selections(s) to cubes matching at least one of the provided values];

docargument [T] [Type of filter applied to selection(s)] [] [0];

docargument [V] [Up to seven values to match, depending on the filter] [] [1];

docremark [The following filters are supported:];
docremark [];
docremark [Textures:]
docremark [FLOOR_T, WALL_T, UPWALL_T, CEIL_T, where V is the slot number];
docremark [];
docremark [Heights:]
docremark [FLOOR_H, CEIL_H, where V is the height];
docremark [];
docremark [Types:]
docremark [SOLID, HEIGHTFIELD, CORNER, SPACE, where (V > 0) is matching the type, and (V <= 0) excluding the type];
docremark [];
docremark [Functions:]
docremark [DISSOLVE, where V is the percent of cubes to randomly remove from the selection(s)];
docremark [SUBTRACT, where V is the number of selections to subtract from the selection(s)];
docremark [MERGE, where V is unused];
docremark [];

docexample [filter_sel CEIL_T 0] [selects only cubes in the current selection(s) with the skymap as the ceiling texture];
docexample [] [];
docexample [filter_sel DISSOLVE 80] [removes 80% of the selection(s) randomly];

//----------------------------------------------------------------------------

docident [filter_sel FLOOR_T] [filters selections(s) to cubes using one of the provided floor textures];

docargument [T] [Up to seven texture slots to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel WALL_T] [filters selections(s) to cubes using one of the provided wall textures];

docargument [T] [Up to seven texture slots to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel UPWALL_T] [filters selections(s) to cubes using one of the provided upper wall textures];

docargument [T] [Up to seven texture slots to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel CEIL_T] [filters selections(s) to cubes using one of the provided ceiling textures];

docargument [T] [Up to seven texture slots to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel FLOOR_H] [filters selections(s) to cubes with one of the provided floor heights];

docargument [H] [Up to seven heights to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel CEIL_H] [filters selections(s) to cubes with one of the provided ceiling heights];

docargument [H] [Up to seven heights to match] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel SOLID] [filters selections(s) to cubes that are/aren't solid];

docargument [V] [valid integer] [] [0];
docremark [V > 0 searches for solids, removing non-solids]
docremark [V <= 0 searches for non-solids, removing solids]

//----------------------------------------------------------------------------

docident [filter_sel HEIGHTFIELD] [filters selections(s) to cubes that are/aren't heightfields];

docargument [V] [valid integer] [] [0];
docremark [V > 0 searches for heightfields, removing non-heightfields]
docremark [V <= 0 searches for non-heightfields, removing heightfields]

//----------------------------------------------------------------------------

docident [filter_sel CORNER] [filters selections(s) to cubes that are/aren't corners];

docargument [V] [valid integer] [] [0];
docremark [V > 0 searches for corners, removing non-corners]
docremark [V <= 0 searches for non-corners, removing corners]

//----------------------------------------------------------------------------

docident [filter_sel SPACE] [filters selections(s) to cubes that are/aren't spaces];

docargument [V] [valid integer] [] [0];
docremark [V > 0 searches for spaces, removing non-spaces]
docremark [V <= 0 searches for non-spaces, removing spaces]
docremark []
docremark ["Space" cubes are cubes not matching any other unique type, e.g. heightfields]

//----------------------------------------------------------------------------

docident [filter_sel DISSOLVE] [Removes P percent of selection(s) randomly];

docargument [P] [percentage of cubes to remove from selections(s)] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel SUBTRACT] [Subtracts the last N selections from all other selections];

docargument [N] [Number of selections to subtract] [] [0];

//----------------------------------------------------------------------------

docident [filter_sel MERGE] [Combines and condenses overlapping selections];

//----------------------------------------------------------------------------

docident [curtex] [Prints texture slot information of the selected cube, or the cube at X / Y];
docargument [P] [Position of texture to check] [FLOOR, WALL, UPWALL, CEIL, ALL] [0];
docargument [X] [X coordinate of cube to check] [optional] [0];
docargument [Y] [Y coordinate of cube to check] [optional] [0];
docremark [If no coordinate arguments are provided, curtex will list textures(s) for the currently selected cube];
docremark [];
docremark [Note: Without x/y input, curtex works with the top-left cube of the most recent selection]
docexample [curtex WALL] [prints the texture slot used for the wall of the selected cube];

//--------------------------------------------

docident [texname] [returns the filename of the selected cube's texture at position P]
docargument [P] [Position of texture to return] [FLOOR, WALL, UPWALL, CEIL] [0];
docexample [echo (texname FLOOR)] [prints the filename of the selected cube's floor texture]

//--------------------------------------------

docident [getcurtex] [returns texture slot number of the selected cube, or the cube at X / Y];
docargument [P] [Position of texture to return] [FLOOR, WALL, UPWALL, CEIL, ALL] [0];
docargument [X] [X coordinate of cube to check] [optional] [0];
docargument [Y] [Y coordinate of cube to check] [optional] [0];
docremark [If no coordinate arguments are provided, getcurtex will list textures(s) for the currently selected cube];
docremark [];
docremark [Note: Without x/y input, getcurtex works with the top-left cube of the most recent selection]
docremark [];
docremark [Returns -1 on error];
docexample [getcurtex FLOOR 100 75] [returns the texture slot used for the floor at position 100/75];

//--------------------------------------------

docident [curheight] [Prints height information of the selected cube, or the cube at X / Y];
docargument [P] [Position of height to check] [FLOOR, CEIL, BOTH] [0];
docargument [X] [X coordinate of cube to check] [optional] [0];
docargument [Y] [Y coordinate of cube to check] [optional] [0];
docremark [If no coordinate arguments are provided, curheight will list height(s) for the currently selected cube];
docremark [];
docremark [Note: Without x/y input, curheight works with the top-left cube of the most recent selection]
docexample [curheight CEIL] [prints the ceiling height of the selected cube];

//--------------------------------------------

docident [getcurheight] [returns the height of the selected cube, or the cube at X / Y];
docargument [P] [Position of height to check] [FLOOR, CEIL] [0];
docargument [X] [X coordinate of cube to check] [optional] [0];
docargument [Y] [Y coordinate of cube to check] [optional] [0];
docremark [If no coordinate arguments are provided, getcurheight will return the height of the currently selected cube];
docremark [];
docremark [Note: Without x/y input, getcurheight works with the top-left cube of the most recent selection]
docremark [];
docremark [Returns -255 on error];
docexample [getcurheight FLOOR 100 75] [returns the floor height at position 100/75];

//----------------------------------------------------------------------------

docident [undo_sel] [removes last N selection(s) from the list of selections];
docargument [N] [Number of selections to remove] [If no argument provided, a value of 1 is used] [0];
docexample [undo_sel 2] [removes the last two selections made from the list of selections.];

//----------------------------------------------------------------------------

docident [select_all] [removes all current selections and selects entire map area, including bounding walls];

//----------------------------------------------------------------------------

docident [equalize_sels] [levels the floor/ceiling of all selections, with the min/max height gathered from all of them];

docargument [T] [an integer denoting the type] [0 (floor), 2 (ceiling)] [0];

docremark [This differs from /equalize in that /equalize_sels levels all selections at once, rather than each selection having it's own min/max height]

//----------------------------------------------------------------------------

docident [clearrecenttex] [clears the history of recent textures used in the current map];

//-------------------------------------------------------------------

docident [cleanedit] [turns on/off clean edit mode];
docargument [V] [0(off)...1(on)] [] [0];
docremark [While editing in clean edit mode, objects/guides not normally visible remain unvisible]
docremark [i.e. The grid guidlines, selection borders, entity sparkles, clips, playerstart arrows, and closest entity information are hidden]

//-------------------------------------------------------------------

docident [togglecleanedit] [inverses the on/off state of cleanedit];

//-------------------------------------------------------------------

docident [savesel] [Stores the list of current selections to later be recalled by /reselect];

//-------------------------------------------------------------------

docident [reselect] [Removes all current selections and selects the list of cubes previously stored with /savesel];

//-------------------------------------------------------------------

docident [tex2front] [Brings the selected cube's texture slot used in position P to the front of its recent textures list];
docargument [P] [The position of the cube's texture to bring to front] [FLOOR, WALL, UPWALL, CEIL] [0];
docremark [tex2front works with the top-left cube of the most recent selection]

//-------------------------------------------------------------------

docident [paste_half] [Pastes the floor/ceiling of a previously copied selection, leaving the opposite half unmodified];
docremark [The choice of floor or ceiling is derived from which the player is looking at]

//-------------------------------------------------------------------

This also allows you to use the in game reference to access all these commands from a menu within AC (main menu -> help -> assaultcube reference -> new)

What I Would Like from You

Players / Mappers:

This is a product of many hours of programming, testing, and troubleshooting. I strongly encourage feedback, which helps me improve on this design. I would like to hear how this client improves your mapping and any features you would like to see in a future version.

Programmers / Advanced Users:

I would like to get Mac and Linux ports for this. It is based on the 1.2.0.2 source.

Diff: hosted on pastebin because of post character limit

Please contact me if you can port then I can add the port(s) to this post.
Thanks given by: zero , It's L'enmerdeur , Toriality
#2
Seems awesome! I go try it out.
Thanks given by:
#3
Do you need a linux binary?
Thanks given by:
#4
nice release!
Thanks given by:
#5
Nicely done (as usual) Felix! Tbh this is long overdue. ;)
Thanks given by:
#6
Linux binary? pls I really want to test it (dat wat she...)
Thanks given by:
#7
(10 Jun 14, 01:35AM)MPx Wrote: Linux binary? pls I really want to test it (dat wat she...)

Once he sends me the diff I'll compile one for him.
Thanks given by:
#8
v63
Thanks given by:
#9
740kb bigger than ac_client, it is 40 % of size, it is strange ...
Thanks given by:
#10
I just downloaded the source and compiled with zero modifications.
1824 KB

My client:
1848 KB

It's my compiler.
Thanks given by:
#11
Why don't you just release the source code?
Thanks given by:
#12
(13 Jun 14, 03:39PM)Music Wrote: Why don't you just release the source code?
^ This
Thanks given by:
#13
(14 Jun 14, 02:23AM)+f0r3v3r+ Wrote:
(13 Jun 14, 03:39PM)Music Wrote: Why don't you just release the source code?
^ This

Diff - based on 1.2.0.2 source

--- audiomanager.cpp    Tue Oct 22 13:57:19 2013
+++ audiomanager.cpp    Wed Apr 02 17:25:06 2014
@@ -554,7 +554,7 @@
// main audio update routine

void audiomanager::updateaudio()
-{
+{
     if(nosound) return;

     alcSuspendContext(context); // don't process sounds while we mess around
@@ -606,7 +606,7 @@
         {
             entity &e = ents[i];
             vec o(e.x, e.y, e.z);
-            if(e.type!=SOUND) continue;
+            if(e.type!=SOUND) continue;

             int sound = e.attr1;
             int radius = e.attr2;
--- clientgame.cpp    Tue Oct 22 13:57:19 2013
+++ clientgame.cpp    Fri Apr 11 14:43:34 2014
@@ -1116,10 +1116,13 @@
COMMAND(showmapstats, "");

VARP(showmodedescriptions, 0, 1, 1);
-extern bool canceldownloads;
+extern bool canceldownloads;
+
+extern bool lockselent;

void startmap(const char *name, bool reset)   // called just after a map load
-{
+{
+    lockselent = false;
     canceldownloads = false;
     copystring(clientmap, name);
     sendmapidenttoserver = true;
--- cube.h    Sat Nov 09 17:56:31 2013
+++ cube.h    Mon May 19 23:38:03 2014
@@ -43,7 +43,8 @@
extern vector<int> eh_ents;             // edithide entities
extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world
extern int lastmillis, totalmillis, nextmillis; // last time
-extern int curtime;                     // current frame time
+extern int curtime;                     // current frame time
+extern int globalfps;
extern int interm;
extern int gamemode, nextmode;
extern int gamespeed;
--- editing.cpp    Tue Oct 22 13:57:16 2013
+++ editing.cpp    Sat Jun 07 00:41:10 2014
@@ -3,12 +3,20 @@
#include "cube.h"

bool editmode = false;
+
+int globalfps = 0;

// the current selections, used by almost all editing commands
// invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map
// => selections are checked when they are made or when the world is reloaded

-vector<block> sels;
+VAR(cleanedit, 0, 0, 1);
+COMMANDF(togglecleanedit, "", () {cleanedit = !cleanedit;});
+
+COMMANDF(clearrecenttex, "", () {loopk(3) loopi(256) hdr.texlists[k][i] = i;});
+
+vector<block> sels;
+vector<block> s_sels; //saved selections

#define loopselxy(sel, b) { makeundo(sel); loop(x,(sel).xs) loop(y,(sel).ys) { sqr *s = S((sel).x+x, (sel).y+y); b; } remip(sel); }
#define loopselsxy(b) { loopv(sels) loopselxy(sels[i], b); }
@@ -70,6 +78,20 @@
     }
     return !editmode;
}
+
+void savesel()
+{
+    s_sels = sels;
+}
+
+void reselect()
+{
+    sels = s_sels;
+}
+
+COMMAND (savesel, "");
+COMMAND (reselect, "");
+

inline bool selset()
{
@@ -81,6 +103,48 @@
     if(!selset()) conoutf("no selection");
     return !selset();
}
+
+const char *entcolor(int index) // helper function for editinfo()
+{
+    entity &e = ents[index];
+
+    switch(e.type)
+    {
+        case 2: // playerstart
+        if (e.attr2 == 0) // CLA
+        return "3";
+
+        else if (e.attr2 == 1) // RVSF
+        return "1";
+
+        else return "J"; // FFA
+
+        case 3: case 4: case 5: case 9: return "9"; // pistol clip, ammobox, grenade, akimbo
+        case 6: case 7: case 8: return "H"; // health pack, helmet, armor
+        case 10: return "T"; // mapmodel
+
+        case 12: return "M"; // ladder
+
+        case 13: // ctf-flag
+        if (e.attr2 == 0) // CLA
+        return "3";
+
+        else if (e.attr2 == 1) // RVSF
+        return "1";
+        break;
+
+        case 14: return "P"; // sound
+
+        case 15: return "2"; // clip
+        case 16: return "X"; // plclip
+
+        default:
+        return "5"; // white
+    }
+    return "5"; // white
+}
+
+extern bool lockselent;

char *editinfo()
{
@@ -88,14 +152,43 @@
     if(!editmode) return NULL;
     int e = closestent();
     if(e<0) return NULL;
-    entity &c = ents[e];
+    entity &c = ents[e];
+
     string selinfo = "no selection";
-    if(selset()) formatstring(selinfo)("selection = (%d, %d)", (sels.last()).xs, (sels.last()).ys);
-    formatstring(info)("closest entity = %s (%d, %d, %d, %d), %s", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo);
+    if(selset()) formatstring(selinfo)("selection = (%d, %d)", (sels.last()).xs, (sels.last()).ys);
+
+    string fileinfo = "";
+    switch (c.type)
+    {
+        case SOUND:
+        if (mapsounds.inrange(c.attr1))
+        formatstring(fileinfo)("\n%s", mapsounds[c.attr1].buf->name);
+        else
+        formatstring(fileinfo)("\n\f7unregistered sound\f5");
+        break;
+
+        case MAPMODEL:
+        {
+            mapmodelinfo &mmi = getmminfo(c.attr2);
+            if (&mmi)
+            formatstring(fileinfo)("\n%s", mmi.name);
+            else
+            formatstring(fileinfo)("\n\f7unregistered mapmodel\f5");
+        }
+        break;
+
+        default:
+        break;
+    }
+
+    if (!lockselent)
+    formatstring(info)("closest entity:\n\f%s%s\f5 (%d, %d, %d, %d), %s %s", entcolor(e), entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo, fileinfo);
+    else
+    formatstring(info)("selected entity:\n\f%s%s\f5 (%d, %d, %d, %d), %s %s", entcolor(e), entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo, fileinfo);
+
     return info;
}

-
#define EDITSEL   if(noteditmode("EDITSEL") || noselection()) return
#define EDITSELMP if(noteditmode("EDITSELMP") || noselection() || multiplayer()) return
#define EDITMP    if(noteditmode("EDITMP") || multiplayer()) return
@@ -150,87 +243,182 @@

// VC8 optimizer screws up rendering somehow if this is an actual function
#define sheight(s,t,z) (!flrceil ? (s->type==FHF ? s->floor-t->vdelta/4.0f : (float)s->floor) : (s->type==CHF ? s->ceil+t->vdelta/4.0f : (float)s->ceil))
-
-void cursorupdate()                                     // called every frame from hud
-{
-    flrceil = ((int)(camera1->pitch>=0))*2;
-    int cyaw = ((int) camera1->yaw) % 180;
-    editaxis = editmode ? (fabs(camera1->pitch) > 65 ? 13 : (cyaw < 45 || cyaw > 135 ? 12 : 11)) : 0;
-
-    volatile float x = worldpos.x;                      // volatile needed to prevent msvc7 optimizer bug?
-    volatile float y = worldpos.y;
-    volatile float z = worldpos.z;
-
-    cx = (int)x;
-    cy = (int)y;
-
-    if(OUTBORD(cx, cy)) return;
-    sqr *s = S(cx,cy);
-
-    if(fabs(sheight(s,s,z)-z)>1)                        // selected wall
-    {
-        x += x>camera1->o.x ? 0.5f : -0.5f;             // find right wall cube
-        y += y>camera1->o.y ? 0.5f : -0.5f;
-
-        cx = (int)x;
-        cy = (int)y;
-
-        if(OUTBORD(cx, cy)) return;
-    }
-
-    if(dragging) { makesel(false); };
-
-    const int GRIDSIZE = 5;
-    const float GRIDW = 0.5f;
-    const float GRID8 = 2.0f;
-    const float GRIDS = 2.0f;
-    const int GRIDM = 0x7;
-
-    // render editing grid
-
-    if(showgrid)
-    {
-        for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++)
-        {
-
-            if(OUTBORD(ix, iy)) continue;
-            sqr *s = S(ix,iy);
-            if(SOLID(s)) continue;
-            float h1 = sheight(s, s, z);
-            float h2 = sheight(s, SWS(s,1,0,sfactor), z);
-            float h3 = sheight(s, SWS(s,1,1,sfactor), z);
-            float h4 = sheight(s, SWS(s,0,1,sfactor), z);
-            if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40);
-            else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80);
-            else linestyle(GRIDW, 0x80, 0x80, 0x80);
-            block b = { ix, iy, 1, 1 };
-            box(b, h1, h2, h3, h4);
-            linestyle(GRID8, 0x40, 0x40, 0xFF);
-            if(!(ix&GRIDM))   line(ix,   iy,   h1, ix,   iy+1, h4);
-            if(!((ix+1)&GRIDM)) line(ix+1, iy,   h2, ix+1, iy+1, h3);
-            if(!(iy&GRIDM))   line(ix,   iy,   h1, ix+1, iy,   h2);
-            if(!((iy+1)&GRIDM)) line(ix,   iy+1, h4, ix+1, iy+1, h3);
-        }
-
-        if(!SOLID(s))
-        {
-            float ih = sheight(s, s, z);
-            linestyle(GRIDS, 0xFF, 0xFF, 0xFF);
-            block b = { cx, cy, 1, 1 };
-            box(b, ih, sheight(s, SWS(s,1,0,sfactor), z), sheight(s, SWS(s,1,1,sfactor), z), sheight(s, SWS(s,0,1,sfactor), z));
-            linestyle(GRIDS, 0xFF, 0x00, 0x00);
-            dot(cx, cy, ih);
-            ch = (int)ih;
-        }
-    }
-
-    if(selset())
-    {
-        linestyle(GRIDS, 0xFF, 0x40, 0x40);
-        loopv(sels) box(sels[i], (float)sels[i].h, (float)sels[i].h, (float)sels[i].h, (float)sels[i].h);
-    }
-
-    glLineWidth(1);
+
+int gridalpha = 255;
+int delay = 128;
+float old_p, old_y;
+
+void cursorupdate()                                     // called every frame from hud
+{
+    if (old_p != camera1->pitch || old_y != camera1->yaw)
+    {gridalpha = 255; delay = 128;}
+    else
+    {
+        delay -= ((255 / globalfps) + 1 );
+        if (delay < 0) delay = 0;
+
+        if (!delay)
+        gridalpha -= ((gridalpha * 3 / globalfps) + 1 );
+
+        if (gridalpha < 0)
+        gridalpha = 0;
+    }
+
+    if (!gridalpha)
+    return;
+
+    old_p = camera1->pitch;
+    old_y = camera1->yaw;
+
+    flrceil = ((int)(camera1->pitch>=0))*2;
+    int cyaw = ((int) camera1->yaw) % 180;
+    editaxis = editmode ? (fabs(camera1->pitch) > 65 ? 13 : (cyaw < 45 || cyaw > 135 ? 12 : 11)) : 0;
+
+    volatile float x = worldpos.x;                      // volatile needed to prevent msvc7 optimizer bug?
+    volatile float y = worldpos.y;
+    volatile float z = worldpos.z;
+
+    cx = (int)x;
+    cy = (int)y;
+
+    if (OUTBORD(cx, cy)) return;
+    sqr *s = S(cx,cy);
+
+    if(fabs(sheight(s,s,z)-z)>1)                        // selected wall
+    {
+        x += x>camera1->o.x ? 0.5f : -0.5f;             // find right wall cube
+        y += y>camera1->o.y ? 0.5f : -0.5f;
+
+        cx = (int)x;
+        cy = (int)y;
+
+        if(OUTBORD(cx, cy)) return;
+    }
+
+    if(dragging) { makesel(false); };
+
+    const int GRIDSIZE = 5;
+    const float GRIDW = 0.5f;
+    const float GRID8 = 2.0f;
+    const float GRIDS = 2.0f;
+    const int GRIDM = 0x7;
+
+    // render editing grid
+
+    glEnable(GL_BLEND);
+    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++)
+    {
+        if(OUTBORD(ix, iy)) continue;
+        sqr *s = S(ix,iy);
+
+        //if(SOLID(s)) continue;
+
+        float h1 = sheight(s, s, z);
+        float h2 = sheight(s, SWS(s,1,0,sfactor), z);
+        float h3 = sheight(s, SWS(s,1,1,sfactor), z);
+        float h4 = sheight(s, SWS(s,0,1,sfactor), z);
+
+        /** "Tag" cube style **/
+
+        if(s->tag)
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x40, 0x40, 0xFF, (unsigned char)gridalpha);
+        }
+
+        /** Heightfield Style **/
+
+        else if(s->type==FHF || s->type==CHF)
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x80, 0xFF, 0x80, (unsigned char)gridalpha); //green
+        }
+
+        /** Solid cube style **/
+
+        else if(SOLID(s))
+        {
+            glLineWidth(GRID8);
+            glColor4ub(0xAB, 0x40, 0xFE, (unsigned char)gridalpha); //purple
+        }
+
+        /** Normal cube style **/
+
+        else
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x80, 0x80, 0x80, (unsigned char)gridalpha); //grey
+        }
+
+        /** Actually drawing the grid **/
+
+        block b = { ix, iy, 1, 1 }; //set destination coordinates to draw to
+        box(b, h1, h2, h3, h4); //draw
+
+        /** The repeating 8 x 8 grid **/
+
+        /* Choosing color **/
+
+        int temp = 255 - ((255-gridalpha)*3/2); //the thicker blue appears more opaque than other lines
+        if (temp < 0) temp = 0;
+
+        glLineWidth(GRID8);
+        glColor4ub(0x40, 0x40, 0xFF, (unsigned char)temp); //blue
+
+        /* Drawing lines on this grid */
+
+        if(!(ix&GRIDM))
+        line(ix, iy, h1, ix, iy+1, h4);
+
+        if(!((ix+1)&GRIDM))
+        line(ix+1, iy, h2, ix+1, iy+1, h3);
+
+        if(!(iy&GRIDM))
+        line(ix, iy, h1, ix+1, iy, h2);
+
+        if(!((iy+1)&GRIDM))
+        line(ix, iy+1, h4, ix+1, iy+1, h3);
+    }
+
+    /** Draw white square around cube cursor is over **/
+
+
+    float ih = sheight(s, s, z);
+    {
+        glLineWidth(GRIDS);
+        glColor4ub(0xFF, 0xFF, 0xFF, (unsigned char)gridalpha); //white
+    }
+
+    block b = { cx, cy, 1, 1 };
+    box(b, ih, sheight(s, SWS(s,1,0,sfactor), z), sheight(s, SWS(s,1,1,sfactor), z), sheight(s, SWS(s,0,1,sfactor), z));
+
+    /** And the orienting little square in the corner **/
+
+    glLineWidth(GRIDS);
+    glColor4ub(0xFF, 0x00, 0x00, (unsigned char)gridalpha); //red
+    dot(cx, cy, ih);
+
+    ch = (int)ih; //updates height of selection box (the red boxes) to the correct height
+
+    /** Draw selection(s) **/
+
+    if(selset())
+    {
+        /* Choosing color */
+
+        glLineWidth(GRIDS);
+        glColor4ub(0xFF, 0x40, 0x40, (unsigned char)gridalpha); //red
+
+        /* Drawing */
+
+        loopv(sels) //for each selection
+        box(sels[i], (float)sels[i].h, (float)sels[i].h, (float)sels[i].h, (float)sels[i].h);
+    }
+
+    glDisable(GL_BLEND);
+    glLineWidth(1);
}

vector<block *> undos;                                  // unlimited undo
@@ -308,6 +496,44 @@
         remipmore(sel);
     }
}
+
+
+void paste_half()
+{
+    if(noteditmode("EDITSEL") || noselection()) return;
+
+    if(!copybuffers.length()) { conoutf("nothing to paste"); return; }
+
+    loopv(sels)
+    {
+        block &sel = sels[i];
+        int selx = sel.x;
+        int sely = sel.y;
+
+        loopvj(copybuffers)
+        {
+            block *copyblock = copybuffers[j];
+            int dx = copyblock->x - copybuffers[0]->x, dy = copyblock->y - copybuffers[0]->y;
+
+            sel.xs = copyblock->xs;
+            sel.ys = copyblock->ys;
+            sel.x = selx + dx;
+            sel.y = sely + dy;
+
+            if(!correctsel(sel) || sel.xs!=copyblock->xs || sel.ys!=copyblock->ys) { conoutf("incorrect selection"); return; }
+            makeundo(sel);
+
+            if (flrceil == 0)
+            halfblockpaste(*copyblock, sel.x, sel.y, true);
+            else
+            halfblockpaste(*copyblock, sel.x, sel.y, false);
+        }
+
+        remipmore(sel);
+    }
+}
+
+COMMAND(paste_half, "");

// Count the walls of type "type" contained in the current selection
void countwalls(int *type)
@@ -338,6 +564,46 @@
         }
     }
}
+
+int getcurtex(const char* pos, const char* x, const char* y); //prototype
+
+void tex2front(const char* pos)
+{
+    if (noselection())
+    return;
+
+    int t = -1; //type (position)
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 3;
+    else if (strcmp(pos, "CEIL") == 0) t = 2;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    if (t == 3) t = 1; //walls
+
+    int key = getcurtex(pos, "", "");
+    //conoutf("Key is texture slot %i", key);
+    //conoutf("Searching edit texture lists for match...");
+
+    loopi(255)
+    {
+        if (hdr.texlists[t][i] == key)
+        {
+            //conoutf("Match found in position: %i", i);
+            curedittex[t] = i;
+            break;
+        }
+    }
+
+    tofronttex();
+}
+
+COMMAND (tex2front, "s");

void editdrag(bool isdown)
{
@@ -454,6 +720,1002 @@
         addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t);
     }
}
+
+int expandsel(int s) //makes all selections collections of 1 x 1 selections
+{
+    int count = 0; //number of cubes in selections to remove
+
+    int stop = sels.length();
+    int removed = 0;
+
+    for (int i = 0; i < stop; i++)
+    {
+        for (int y = sels[i].y; y < sels[i].y + sels[i].ys; y++) //y loop
+        for (int x = sels[i].x; x < sels[i].x + sels[i].xs; x++) //x loop
+        {
+            addselection(x, y, 1, 1, sels[i].h);
+
+            if (i >= stop - s) //selections to remove
+            count++;
+        }
+
+        sels.remove(i);
+        i--;
+        stop--;
+        removed++;
+    }
+
+
+    return count;
+}
+
+void compress_sel(int s) //combines adjacent selections (1 x 1 selections only)
+{
+    EDITSEL;
+
+    int min_x = 0;
+    int min_y = 0;
+
+    int max_x = 0;
+    int max_y = 0;
+    int max_h = 0;
+
+    int x;
+    int y;
+    int h;
+
+    /** Initialize selections map **/
+
+    loopv(sels)
+    {
+        if (i == 0) //initialize bounds
+        {
+            min_x = sels[i].x + sels[i].xs;
+            max_x = sels[i].x + sels[i].xs;
+            min_y = sels[i].y + sels[i].ys;
+            max_y = sels[i].y + sels[i].ys;
+            max_h = sels[i].h;
+        }
+
+        x = sels[i].x + sels[i].xs;
+        y = sels[i].y + sels[i].ys;
+        h = sels[i].h;
+
+        if (x < min_x) min_x = x;
+
+        if (x > max_x) max_x = x;
+
+        if (y < min_y) min_y = y;
+
+        if (y > max_y) max_y = y;
+
+        if (h > max_h) max_h = h;
+    }
+
+    bool cubemap [2 + (max_y - min_y)][2 + (max_x - min_x)];
+    memset(cubemap, false, sizeof(cubemap));
+
+    for(int i = 0; i<sels.length(); i++)
+    {
+        if (i < sels.length()-s)
+        cubemap[sels[i].y + sels[i].ys - min_y] [sels[i].x + sels[i].xs - min_x] = true;
+        else
+        cubemap[sels[i].y + sels[i].ys - min_y] [sels[i].x + sels[i].xs - min_x] = false;
+    }
+
+    /** reselect rows of cubes according to map **/
+
+    resetselections();
+
+    int slice_start; //first cube in a horizontal selection slice
+
+    for (y = 0; y < (max_y - min_y) + 1; y++)
+    {
+        slice_start = -1;
+
+        for (x = 0; x < (max_x - min_x) + 1; x++)
+        {
+            if (cubemap[y][x])
+            {
+                if (slice_start < 0)
+                slice_start = x;
+            }
+            else
+            {
+                if (slice_start > -1)
+                addselection(min_x + slice_start - 1, min_y + y - 1, x - slice_start, 1, max_h);
+
+                slice_start = -1;
+                continue;
+            }
+
+            if ((x + 1 >= (max_x - min_x) + 1) && slice_start > -1)
+            addselection(min_x + slice_start - 1, min_y + y - 1, x - slice_start + 1, 1, max_h);
+        }
+    }
+
+    /** combine rows of equal length vertically adjacent to each other **/
+
+    int y2;
+
+    loopv (sels) //i loop
+    loopvj (sels) //j loop
+    {
+        if (sels[i].x == sels[j].x) //same x coordinate
+        if (sels[i].y == sels[j].y + sels[j].ys || sels[j].y == sels[i].y + sels[i].ys) //vertically adjacent
+        if (sels[i].xs == sels[j].xs) //same length
+        {
+            min_y = sels[i].y;
+            max_y = sels[i].y + sels[i].ys;
+            max_h = sels[i].h;
+
+            y = sels[j].y;
+            y2 = sels[j].y + sels[j].ys;
+            h = sels[j].h;
+
+            if (y < min_y) min_y = y;
+            if (y > max_y) max_y = y;
+            if (y2 < min_y) min_y = y2;
+            if (y2 > max_y) max_y = y2;
+            if (h > max_h) max_h = h;
+
+            addselection(sels[i].x, min_y, sels[i].xs, max_y - min_y, max_h);
+
+            sels.remove(i);
+
+            if (i < j)  //if deleting i offset j
+            sels.remove(j - 1);
+            else
+            sels.remove(j);
+
+            i = 0;
+            break; //exit this j loop, go to next j loop, of i loop
+        }
+    }
+}
+
+void fixselh ()
+{
+    bool fc;
+
+    if (flrceil == 0) fc = false; //floor
+    else fc = true;
+
+    sqr *s1;
+    sqr *s2;
+
+    loopv (sels)
+    {
+        s1 = S(sels[i].x, sels[i].y);
+        s2 = S(sels[i].x + sels[i].xs - 1, sels[i].y + sels[i].ys - 1);
+
+        if(!fc) //finding max floor height
+        {
+            if (s1->floor > s2->floor)
+            sels[i].h = s1->floor;
+            else
+            sels[i].h = s2->floor;
+        }
+        else //finding max ceiling height
+        {
+            if (s1->ceil > s2->ceil)
+            sels[i].h = s1->ceil;
+            else
+            sels[i].h = s2->ceil;
+        }
+    }
+}
+
+/** expand selections and select only selections matching argument keys **/
+
+void filter_sel(const char* type, char* v1, char* v2, char* v3, char* v4, char* v5, char* v6, char* v7)
+{
+    EDITSEL;
+
+    /** Process filter type **/
+
+    int filter_type = 0;
+
+    if (strcmp(type, "FLOOR_T") == 0) filter_type = 1;
+    if (strcmp(type, "WALL_T") == 0) filter_type = 2;
+    if (strcmp(type, "UPWALL_T") == 0) filter_type = 3;
+    if (strcmp(type, "CEIL_T") == 0) filter_type = 4;
+    if (strcmp(type, "FLOOR_H") == 0) filter_type = 5;
+    if (strcmp(type, "CEIL_H") == 0) filter_type = 6;
+    if (strcmp(type, "DISSOLVE") == 0) filter_type = 7;
+    if (strcmp(type, "SOLID") == 0) filter_type = 8;
+    if (strcmp(type, "HEIGHTFIELD") == 0) filter_type = 9;
+    if (strcmp(type, "CORNER") == 0) filter_type = 10;
+    if (strcmp(type, "SPACE") == 0) filter_type = 11;
+    if (strcmp(type, "SUBTRACT") == 0) filter_type = 12;
+    if (strcmp(type, "MERGE") == 0) filter_type = 13;
+
+    if (filter_type == 0)
+    {
+        conoutf("\f9ERROR: \f5invalid filter type");
+        return;
+    }
+
+    /** Count value arguments **/
+
+    int num_v = 0;
+
+    if (strcmp(v1, "") != 0) num_v++;
+
+    if (strcmp(v2, "") != 0) num_v++;
+
+    if (strcmp(v3, "") != 0) num_v++;
+
+    if (strcmp(v4, "") != 0) num_v++;
+
+    if (strcmp(v5, "") != 0) num_v++;
+
+    if (strcmp(v6, "") != 0) num_v++;
+
+    if (strcmp(v7, "") != 0) num_v++;
+
+    if (num_v < 1 && filter_type != 12 && filter_type != 13)
+    {
+        conoutf("\f9ERROR: \f5this filter requires at least one key value");
+        return;
+    }
+
+    int v [num_v];
+
+    if (num_v > 0) v[0] = atoi(v1);
+    if (num_v > 1) v[1] = atoi(v2);
+    if (num_v > 2) v[2] = atoi(v3);
+    if (num_v > 3) v[3] = atoi(v4);
+    if (num_v > 4) v[4] = atoi(v5);
+    if (num_v > 5) v[5] = atoi(v6);
+    if (num_v > 6) v[6] = atoi(v7);
+
+    int count = 0;
+
+    if (strcmp(v1, "") != 0)
+    count = expandsel(v[0]); //expand selections to 1 x 1 selections
+    else
+    count = expandsel(1);
+
+    /** filter selection(s) */
+
+    bool match; //match found
+
+    loopv(sels)
+    {
+        sqr *s = S((sels[i]).x, (sels[i]).y);
+        match = false;
+
+        switch(filter_type)
+        {
+            case 1: //floor texture
+            loopj (num_v)
+            {
+                if (s->ftex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 2: //wall texture
+            loopj (num_v)
+            {
+                if (s->wtex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 3: // upper wall texture
+            loopj (num_v)
+            {
+                if (s->utex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 4: //ceiling texture
+            loopj (num_v)
+            {
+                if (s->ctex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 5: //floor height
+            loopj (num_v)
+            {
+                if (s->floor == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 6: //ceiling height
+            loopj (num_v)
+            {
+                if (s->ceil == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 7: //dissolve
+            if (1 + rand()%99 < v[0]) //frequency
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 8: //solid
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == SOLID)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != SOLID)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 9: //heightfield
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == FHF || s->type == CHF)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != FHF && s->type != CHF)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 10: //corner
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == CORNER)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != CORNER)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 11: //space
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == SPACE)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != SPACE)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            default:
+            break;
+        }
+    }
+
+    if (!sels.empty()) //if there is a selection to simplify
+    {
+        if (filter_type == 12 && count > 0) //remove
+        compress_sel(count);
+        else
+        compress_sel(0);
+    }
+
+    fixselh();
+}
+
+COMMAND (filter_sel, "ssssssss");
+
+extern float Mh;
+
+void select_all()
+{
+    resetselections();
+    addselection(mapdims[0] - 1, mapdims[1] - 1, mapdims[2] - mapdims[0] + 3, mapdims[3] - mapdims[1] + 3, Mh);
+}
+
+COMMAND (select_all, "");
+
+struct Slot
+{
+    string name;
+    float scale;
+    Texture *tex;
+    bool loaded;
+};
+
+extern vector<Slot> slots;
+
+void texname(const char* pos)
+{
+    if (noselection())
+    {
+        result("first make a selection");
+        return;
+    }
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-1");
+        return;
+    }
+
+    s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+
+    if (!s)
+    {
+        result("error");
+        return;
+    }
+
+    switch (t)
+    {
+        case 0:
+        result(slots[s->ftex].tex->name);
+        break;
+
+        case 1:
+        result(slots[s->wtex].tex->name);
+        break;
+
+        case 2:
+        result(slots[s->utex].tex->name);
+        break;
+
+        case 3:
+        result(slots[s->ctex].tex->name);
+        break;
+
+        default:
+        result("error");
+    }
+}
+
+COMMAND(texname, "s");
+
+int getcurtex(const char* pos, const char* x, const char* y) //returns index of texture slot
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args == 1) //working from selection
+    {
+        if (noselection())
+        {
+            result("-1");
+            return -1;
+        }
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        result("-1");
+        return -1;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-1");
+        return -1;
+    }
+
+    /**/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            result("-1");
+            return -1;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        result("-1");
+        return -1;
+    }
+
+    string buf = "";
+
+    switch (t)
+    {
+        case 0:
+        concatformatstring(buf, "%d ", s->ftex);
+        result(buf);
+        return s->ftex;
+
+        case 1:
+        concatformatstring(buf, "%d ", s->wtex);
+        result(buf);
+        return s->wtex;
+
+        case 2:
+        concatformatstring(buf, "%d ", s->utex);
+        result(buf);
+        return s->utex;
+
+        case 3:
+        concatformatstring(buf, "%d ", s->ctex);
+        result(buf);
+        return s->ctex;
+
+        default:
+        result("-1");
+        return -1;
+    }
+
+    result("-1");
+    return -1;
+}
+
+void curtex(const char* pos, const char* x, const char* y) //prints texture slot info
+{
+    /** Count arguments **/
+
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        return;
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        return;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else if (strcmp(pos, "ALL") == 0 || num_args == 0) t = 4;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    /**/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 0:
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            return;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        return;
+    }
+
+    switch (t)
+    {
+        case 0:
+        conoutf("The \fPfloor \f5texture of this cube is slot number \fN%i", s->ftex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 1:
+        conoutf("The \fPwall \f5texture of this cube is slot number \fN%i", s->wtex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 2:
+        conoutf("The \fPupper wall \f5texture of this cube is slot number \fN%i", s->utex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 3:
+        conoutf("The \fPceiling \f5texture of this cube is slot number \fN%i", s->ctex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 4:
+        conoutf("The \fPfloor \f5texture of this cube is slot number \fN%i", s->ftex);
+        conoutf("The \fPwall \f5texture of this cube is slot number \fN%i", s->wtex);
+        conoutf("The \fPupper wall \f5texture of this cube is slot number \fN%i", s->utex);
+        conoutf("The \fPceiling \f5texture of this cube is slot number \fN%i", s->ctex);
+        conoutf("\fM--------------------------------------------------------------");
+
+        default:
+        return;
+    }
+}
+
+COMMAND(curtex, "sss");
+COMMAND(getcurtex, "sss");
+
+int getcurheight(const char* pos, const char* x, const char* y) //returns height of cubes
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        {
+            result("-255");
+            return -255;
+        }
+
+        if (num_args == 0)
+        {
+            conoutf("\f9ERROR: \f5getcurheight cannot be called with no arguments");
+            result("-255");
+            return -255;
+        }
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        result("-255");
+        return -255;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int p = -1; //position
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) p = 0;
+    else if (strcmp(pos, "CEIL") == 0) p = 1;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-255");
+        return -255;
+    }
+
+    /** Grab cube **/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            result("-255");
+            return -255;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        result("-255");
+        return -255;
+    }
+
+    /** get height **/
+
+    string buf = "";
+
+    switch (p)
+    {
+        case 0:
+        concatformatstring(buf, "%d ", s->floor);
+        result(buf);
+        return s->floor;
+
+        case 1:
+        concatformatstring(buf, "%d ", s->ceil);
+        result(buf);
+        return s->ceil;
+
+        default:
+        result("-255");
+        return -255;
+    }
+
+    result("-255");
+    return -255;
+}
+
+void curheight(const char* pos, const char* x, const char* y) //prints height of cubes
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        return;
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        return;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int p = -1; //position
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) p = 0;
+    else if (strcmp(pos, "CEIL") == 0) p = 1;
+    else if (num_args == 0 || strcmp(pos, "BOTH") == 0) p = 2;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    /** Grab cube **/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 0:
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            return;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        return;
+    }
+
+    /** get height **/
+
+    switch (p)
+    {
+        case 0:
+        conoutf("The \fPfloor \f5height of this cube is \fN%i", s->floor);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 1:
+        conoutf("The \fPceiling \f5height of this cube is \fN%i", s->ceil);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 2:
+        conoutf("The \fPfloor \f5height of this cube is \fN%i", s->floor);
+        conoutf("The \fPceiling \f5height of this cube is \fN%i", s->ceil);
+        conoutf("\fM--------------------------------------------------------------");
+
+        default:
+        return;
+    }
+}
+
+COMMAND(getcurheight, "sss");
+COMMAND(curheight, "sss");
+
+void undo_sel(char* n) //removes last selection
+{
+    if (sels.empty())
+    {
+        conoutf("no selections to remove");
+        return;
+    }
+
+    int num;
+
+    if (strcmp(n, "") != 0) num = atoi(n);
+    else
+    num = 1;
+
+    while (num > 0)
+    {
+        if (sels.empty())
+        {
+            conoutf("no more selections to remove");
+            return;
+        }
+
+        sels.pop();
+        num--;
+    }
+}
+
+COMMAND (undo_sel, "s");

void replace()
{
@@ -528,6 +1790,42 @@
}

COMMAND(equalize, "i");
+
+void equalize_sels(int *flr) //equalizes *all* sels instead of *each* sel
+{
+    bool isfloor = *flr==0;
+    EDITSEL;
+
+    int hi = 0;
+    int low = 0;
+
+    loopv(sels) //collect min/max info
+    {
+        block &sel = sels[i];
+
+        loopselxy(sel,
+        {
+            if(s->floor<low) low = s->floor;
+            if(s->ceil>hi) hi = s->ceil;
+        });
+    }
+
+    loopv(sels) //height asignments
+    {
+        block &sel = sels[i];
+
+        loopselxy(sel,
+        {
+            if(isfloor) s->floor = low; else s->ceil = hi;
+            if(s->floor>=s->ceil) s->floor = s->ceil-1;
+        });
+
+        addmsg(SV_EDITE, "ri5", sel.x, sel.y, sel.xs, sel.ys, isfloor);
+    }
+
+}
+
+COMMAND(equalize_sels, "i");

void setvdeltaxy(int delta, block &sel)
{
--- entities.cpp    Tue Oct 22 13:57:16 2013
+++ entities.cpp    Fri May 23 02:52:06 2014
@@ -3,7 +3,9 @@
#include "cube.h"

VAR(showclips, 0, 1, 1);
-VAR(showmodelclipping, 0, 0, 1);
+VAR(showmodelclipping, 0, 0, 1);
+
+extern bool cleanedit;

vector<entity> ents;
vector<int> eh_ents; // edithide entities
@@ -26,7 +28,10 @@
  }

void renderclip(entity &e)
-{
+{
+    if (cleanedit)
+    return;
+
     float xradius = max(float(e.attr2), 0.1f), yradius = max(float(e.attr3), 0.1f);
     vec bbmin(e.x - xradius, e.y - yradius, float(S(e.x, e.y)->floor+e.attr1)),
         bbmax(e.x + xradius, e.y + yradius, bbmin.z + max(float(e.attr4), 0.1f));
@@ -124,7 +129,10 @@
COMMAND(seteditshow, "s");

void renderentarrow(const entity &e, const vec &dir, float radius)
-{
+{
+    if (cleanedit)
+    return;
+
     if(radius <= 0) return;
     float arrowsize = min(radius/8, 0.5f);
     vec epos(e.x, e.y, e.z);
@@ -153,11 +161,13 @@
     glEnable(GL_CULL_FACE);
     glEnable(GL_TEXTURE_2D);
}
+
+extern int globalfps;

void renderentities()
{
     int closest = editmode ? closestent() : -1;
-    if(editmode && !reflecting && !refracting && !stenciling)
+    if(editmode && !reflecting && !refracting && !stenciling && !cleanedit)
     {
         static int lastsparkle = 0;
         if(lastmillis - lastsparkle >= 20)
@@ -174,29 +184,49 @@
                 vec v(e.x, e.y, e.z);
                 if(vec(v).sub(camera1->o).dot(camdir) < 0) continue;
                 //particle_splash(i == closest ? PART_ELIGHT : PART_ECLOSEST, 2, 40, v);
-                int sc = PART_ECARROT; // "carrot" (orange) - entity slot currently unused, possibly "reserved"
+                int sc = PART_ELIGHT;
                 if(i==closest)
                 {
-                    sc = PART_ECLOSEST; // blue
-                }
-                else switch(e.type)
+                    sc = PART_ECLOSEST; // smoke
+                    particle_splash(PART_ECLOSEST, 1, 80, v);
+                }
+
+                switch(e.type)
                 {
-                    case LIGHT : sc = PART_ELIGHT; break; // white
-                    case PLAYERSTART: sc = PART_ESPAWN; break; // green
+                    case LIGHT : sc = PART_ELIGHT; break; // white
+
+                    case PLAYERSTART:
+                    if (e.attr2 == 0)
+                    {sc = PART_RED; break;} // CLA
+                    else if (e.attr2 == 1)
+                    {sc = PART_BLUE; break;} // RVSF
+                    else
+                    sc = PART_GREEN; break; // FFA
+
                     case I_CLIPS:
-                    case I_AMMO:
-                    case I_GRENADE: sc = PART_EAMMO; break; // red
+                    case I_AMMO:
+                    case I_AKIMBO:
+                    case I_GRENADE: sc = PART_EAMMO; break; // orange
                     case I_HEALTH:
                     case I_HELMET:
-                    case I_ARMOUR:
-                    case I_AKIMBO: sc = PART_EPICKUP; break; // yellow
-                    case MAPMODEL:
-                    case SOUND: sc = PART_EMODEL; break; // magenta
-                    case LADDER:
-                    case CLIP:
-                    case PLCLIP: sc = PART_ELADDER; break; // grey
-                    case CTF_FLAG: sc = PART_EFLAG; break; // turquoise
-                    default: break;
+                    case I_ARMOUR: sc = PART_EPICKUP; break; // yellow
+                    case MAPMODEL: sc = PART_EMODEL; break; // magenta
+                    case LADDER: sc = PART_ELADDER; break; // grey
+
+                    case CLIP: sc = PART_CLIP; break; // yellow (color of the clip)
+                    case PLCLIP: sc = PART_PLCLIP; break; // magenta (color of the clip)
+
+                    case CTF_FLAG:
+                    if (e.attr2 == 0) // CLA
+                    {sc = PART_RED; break;}
+                    else if (e.attr2 == 1) // RVSF
+                    {sc = PART_BLUE; break;}
+                    else
+                    {sc = PART_ELIGHT; break;}
+
+                    default: break;
+
+                    case SOUND: sc = 22; break; // cyan
                 }
                 //particle_splash(sc, i==closest?6:2, i==closest?120:40, v);
                 particle_splash(sc, 2, 40, v);
@@ -240,8 +270,14 @@
             switch(e.type)
             {
                 case PLAYERSTART:
-                {
-                    glColor3f(0, 1, 1);
+                {
+                    if (e.attr2 == 0)
+                    glColor3f(1, 0, 0); // CLA
+                    else if (e.attr2 == 1)
+                    glColor3f(0, 0, 1); // RVSF
+                    else
+                    glColor3f(0, 1, 0); // FFA
+
                     vec dir;
                     vecfromyawpitch(e.attr1, 0, -1, 0, dir);
                     renderentarrow(e, dir, 4);
--- main.cpp    Tue Oct 29 04:33:15 2013
+++ main.cpp    Mon May 19 23:38:43 2014
@@ -1280,7 +1280,9 @@
         serverslice(0);

         if(elapsed) fps = (1000.0f/elapsed+fps*10)/11; // avoid DIV-by-0
-        frames++;
+        frames++;
+
+        globalfps = fps;

         audiomgr.updateaudio();

@@ -1322,4 +1324,4 @@
}

VAR(version, 1, AC_VERSION, 0);
-VAR(protocol, 1, PROTOCOL_VERSION, 0);
\ No newline at end of file
+VAR(protocol, 1, PROTOCOL_VERSION, 0);
--- Makefile    Sat Nov 09 18:12:11 2013
+++ Makefile    Wed Apr 16 20:37:19 2014
@@ -1,6 +1,12 @@
-CXXFLAGS= -O3 -fomit-frame-pointer
-CXX=clang++    # Use clang++, as g++ optimizations cause crashes...
-override CXXFLAGS+= -Wall -fsigned-char
+
+
+CXXFLAGS= -O3 -fomit-frame-pointer -Wno-unused-variable
+ifneq (,$(findstring MINGW,$(PLATFORM)))
+  CXXFLAGS+= -Wall -fsigned-char
+else
+  CXX=clang++   # Use clang++, as g++ optimizations cause crashes...
+  override CXXFLAGS+= -Wall -fsigned-char
+endif

PLATFORM= $(shell uname -s)
PLATFORM_PREFIX=native
@@ -392,3 +398,4 @@
master-standalone.o: cube.h platform.h tools.h geom.h model.h protocol.h
master-standalone.o: sound.h weapon.h entity.h world.h i18n.h command.h
master-standalone.o: varray.h vote.h console.h protos.h
+
--- physics.cpp    Tue Oct 22 13:57:19 2013
+++ physics.cpp    Thu May 22 20:37:00 2014
@@ -474,7 +474,7 @@
                     }
                 }

-                if(timeinair > 200 && !pl->timeinair)
+                if(timeinair > 200 && !pl->timeinair && !water)
                 {
                     int sound = timeinair > 800 ? S_HARDLAND : S_SOFTLAND;
                     if(pl->state!=CS_DEAD)
--- protos.h    Sat Nov 09 13:48:58 2013
+++ protos.h    Fri May 23 02:52:10 2014
@@ -496,7 +496,8 @@
extern void removedynlights(physent *owner);
extern block *blockcopy(const block &b);
extern void blockpaste(const block &b, int bx, int by, bool light);
-extern void blockpaste(const block &b);
+extern void blockpaste(const block &b);
+extern void halfblockpaste(const block &b, int bx, int by, bool floor);
extern void freeblock(block *&b);

// worldrender
@@ -626,7 +627,7 @@
{
     PART_SPARK = 0,
     PART_SMOKE,
-    PART_ECLOSEST,
+    PART_BLUE,
     PART_BLOOD,
     PART_DEMOTRACK,
     PART_FIREBALL,
@@ -637,13 +638,16 @@
     PART_HUDMUZZLEFLASH,
     PART_MUZZLEFLASH,
     PART_ELIGHT,
-    PART_ESPAWN,
-    PART_EAMMO,
+    PART_GREEN,
+    PART_RED,
     PART_EPICKUP,
     PART_EMODEL,
-    PART_ECARROT,
+    PART_EAMMO,
     PART_ELADDER,
-    PART_EFLAG
+    PART_ECLOSEST,
+    PART_SOUND = 22,
+    PART_CLIP,
+    PART_PLCLIP
};

extern void particleinit();
--- renderhud.cpp    Tue Oct 22 13:57:19 2013
+++ renderhud.cpp    Sat Jun 07 00:20:51 2014
@@ -1,6 +1,8 @@
// renderhud.cpp: HUD rendering

#include "cube.h"
+
+extern bool cleanedit;

void drawicon(Texture *tex, float x, float y, float s, int col, int row, float ts)
{
@@ -278,7 +280,10 @@
COMMAND(loadcrosshair, "ss");

void drawcrosshair(playerent *p, int n, color *c, float size)
-{
+{
+    if (cleanedit && editmode)
+    return;
+
     Texture *crosshair = crosshairs[n];
     if(!crosshair)
     {
@@ -890,7 +895,22 @@
     char *infostr = editinfo();
     int commandh = 1570 + FONTH;
     if(command) commandh -= rendercommand(20, 1570, VIRTW);
-    else if(infostr) draw_text(infostr, 20, 1570);
+
+    else if(infostr)
+    {
+        if (cleanedit)
+        {
+            glPushMatrix();
+            glLoadIdentity();
+            glOrtho(0, VIRTW*2, VIRTH*2, 0, -1, 1);
+            glScalef(1.0, 1.0, 1.0); //set scale
+            draw_text(infostr, 48, VIRTH*2 -4*FONTH);
+            glPopMatrix();
+        }
+        else
+        draw_text(infostr, 20, 1570);
+    }
+
     else if(targetplayer && showtargetname) draw_text(colorname(targetplayer), 20, 1570);
     glLoadIdentity();
     glOrtho(0, VIRTW*2, VIRTH*2, 0, -1, 1);
--- renderparticles.cpp    Tue Oct 22 13:57:19 2013
+++ renderparticles.cpp    Fri May 23 02:58:31 2014
@@ -184,7 +184,7 @@
     }
}

-#define MAXPARTYPES 22
+#define MAXPARTYPES 25

struct particle { vec o, d; int fade, type; int millis; particle *next; };
particle *parlist[MAXPARTYPES], *parempty = NULL;
@@ -252,7 +252,7 @@
{
     { PT_PART,       0.4f, 0.4f, 0.4f, 2,  0, 0.06f }, // yellow: sparks
     { PT_PART,       1.0f, 1.0f, 1.0f, 20, 1, 0.15f }, // grey:   small smoke
-    { PT_PART,       0.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // blue:   edit mode closest ent
+    { PT_PART,       0.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // blue:   used for RVSF related entities
     { PT_BLOOD,      0.5f, 0.0f, 0.0f, 1,  4, 0.3f  }, // red:    blood spats
     { PT_PART,       1.0f, 0.1f, 0.1f, 0,  1, 0.2f  }, // red:    demotrack
     { PT_FIREBALL,   1.0f, 0.5f, 0.5f, 0,  2, 7.0f  }, // explosion fireball
@@ -264,16 +264,19 @@
     { PT_HUDFLASH,   1.0f, 1.0f, 1.0f, 0,  6, 0.7f  }, // hudgun muzzle flash
     { PT_FLASH,      1.0f, 1.0f, 1.0f, 0,  6, 0.7f  }, // muzzle flash
     { PT_PART,       1.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // white: edit mode ent type : light
-    { PT_PART,       0.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // green: edit mode ent type : spawn
-    { PT_PART,       1.0f, 0.0f, 0.0f, 20, 0, 0.08f }, // red: edit mode ent type : ammo
-    { PT_PART,       1.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // yellow: edit mode ent type : pickup
-    { PT_PART,       1.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // magenta: edit mode ent type : model, sound
-    { PT_PART,       1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode ent type : "carrot"
-    { PT_PART,       0.5f, 0.5f, 0.5f, 20, 0, 0.08f }, // grey: edit mode ent type : ladder, (pl)clip
-    { PT_PART,       0.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // turquoise: edit mode ent type : CTF-flag
+    { PT_PART,       0.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // green: edit mode ent type : FFA spawn
+    { PT_PART,       1.0f, 0.0f, 0.0f, 20, 0, 0.08f }, // red: used for CLA related entities
+    { PT_PART,       0.78f, 1.0f, 0.33f, 20, 0, 0.08f }, // yellow-green: edit mode ent type : pickup
+    { PT_PART,       0.6f, 0.3f, 0.9f, 20, 0, 0.08f }, // purple: edit mode ent type : mapmodel
+    { PT_PART,       1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode ent type : ammo
+    { PT_PART,       0.7f, 1.0f, 1.0f, 20, 0, 0.08f }, // bright cyan: edit mode ent type : ladder,
+    { PT_PART,       0.45f, 0.45f, 0.45f, 20, 1, 0.2f }, // smoke effect: closest/selected entity
     // 2011jun18 : shotty decals
     { PT_BULLETHOLE, 0.2f, 0.2f, 1.0f, 0,  3, 0.1f  }, // hole decal M
-    { PT_BULLETHOLE, 0.2f, 1.0f, 0.2f, 0,  3, 0.1f  }, // hole decal C
+    { PT_BULLETHOLE, 0.2f, 1.0f, 0.2f, 0,  3, 0.1f  }, // hole decal C
+    { PT_PART,       0.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // cyan: edit mode ent type: sound
+    { PT_PART,       1.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // yellow: edit mode ent type: clip
+    { PT_PART,       1.0f, 0.0f, 1.0f, 20, 0, 0.08f } // magenta: edit mode ent type: plclip
};

VAR(particlesize, 20, 100, 500);
--- tools.h    Tue Oct 22 13:57:19 2013
+++ tools.h    Wed Apr 02 10:50:08 2014
@@ -424,7 +424,7 @@

     T &pop() { return buf[--ulen]; }
     T &last() { return buf[ulen-1]; }
-    void drop() { buf[--ulen].~T(); }
+    void drop() { buf[--ulen].~T();}
     bool empty() const { return ulen==0; }

     int capacity() const { return alen; }
--- world.cpp    Tue Oct 22 13:57:19 2013
+++ world.cpp    Tue May 20 19:12:15 2014
@@ -127,10 +127,24 @@

COMMAND(nextclosestent, "");
COMMAND(closestenttype, "s");
+
+int active_ent = -1;
+bool locking_ent = false, lockselent = false;
+
+COMMANDF(togglelockselent, "", (void) { active_ent = -1; lockselent = !lockselent; if (lockselent) locking_ent = true;});

int closestent()        // used for delent and edit mode ent display
{
-    if(noteditmode("closestent")) return -1;
+    if(noteditmode("closestent")) return -1;
+
+    if (lockselent && !locking_ent && active_ent != -1)
+    {
+        if (ents[active_ent].type != NOTUSED)
+        return active_ent;
+        else
+        lockselent = false;
+    }
+
     int best = -1, bcnt = 0;
     float bdist = 99999;
     loopj(3)
@@ -150,7 +164,15 @@
             {
                 if(ents[best].x == e.x && ents[best].y == e.y && ents[best].z == e.z)
                 {
-                    if(j == 2 && bcnt == clentsel) return i;
+                    if(j == 2 && bcnt == clentsel)
+                    {
+                        if (locking_ent)
+                        {
+                            active_ent = i;
+                            locking_ent = false;
+                        }
+                        return i;
+                    }
                     bcnt++;
                 }
             }
@@ -162,7 +184,14 @@
         }
         if(best < 0 || bcnt == 1) break;
         if(bcnt) clentsel %= bcnt;
-    }
+    }
+
+    if (locking_ent)
+    {
+        active_ent = best;
+        locking_ent = false;
+    }
+
     return best;
}

@@ -173,7 +202,16 @@
     entity &e = ents[n];
     switch(prop)
     {
-        case 0: e.attr1 += amount; break;
+        case 0:
+        if (e.type == MAPMODEL)
+        {
+            e.attr1 += amount*16;
+            while (e.attr1 > 360) e.attr1 -= 360;
+            while (e.attr1 < 0) e.attr1 += 360;
+        }
+        else e.attr1 += amount;
+        break;
+
         case 1: e.attr2 += amount; break;
         case 2: e.attr3 += amount; break;
         case 3: e.attr4 += amount; break;
@@ -371,6 +409,26 @@
}

COMMANDF(scalelights, "ii", (int *f, int *i) { scalelights(*f, *i); });
+
+void scalelight(int f, int intens)
+{
+    entity &e = ents[closestent()];
+    if(e.type!=LIGHT) return;
+
+    e.attr1 = e.attr1*f/100;
+    if(e.attr1<2) e.attr1 = 2;
+    if(e.attr1>32) e.attr1 = 32;
+
+    if(intens)
+    {
+        scalecomp(e.attr2, intens);
+        scalecomp(e.attr3, intens);
+        scalecomp(e.attr4, intens);
+    }
+    calclight();
+}
+
+COMMANDF(scalelight, "ii", (int *f, int *i) { scalelight(*f, *i); });

int findentity(int type, int index)
{
--- worldlight.cpp    Tue Oct 22 13:57:19 2013
+++ worldlight.cpp    Fri Apr 11 15:30:00 2014
@@ -369,6 +369,49 @@
{
     blockpaste(b, b.x, b.y, false);
}
+
+void halfblockpaste(const block &b, int bx, int by, bool floor)
+{
+    const sqr *q = (const sqr *)((&b)+1);
+    for(int y = by; y<b.ys+by; y++)
+    for(int x = bx; x<b.xs+bx; x++)
+    {
+        if (q->type == SOLID || q->type == CORNER ||
+        //don't override heightfields with space unnessesarily
+        (q->type == SPACE && ((floor && S(x,y)->type != CHF) || (!floor && S(x,y)->type != FHF))))
+
+        S(x,y)->type = q->type;
+
+        if (floor || q->type == SOLID)
+        {
+            if (q->type == FHF ||
+            S(x-1,y)->type == FHF ||
+            S(x,y-1)->type == FHF ||
+            S(x-1,y-1)->type == FHF)
+            //
+            {S(x,y)->type = q->type; S(x,y)->vdelta = q->vdelta;}
+
+            S(x,y)->floor = q->floor;
+            S(x,y)->wtex = q->wtex;
+            S(x,y)->ftex = q->ftex;
+        }
+        else
+        {
+            if (q->type == CHF ||
+            S(x-1,y)->type == CHF ||
+            S(x,y-1)->type == CHF ||
+            S(x-1,y-1)->type == CHF)
+            //
+            {S(x,y)->type = q->type; S(x,y)->vdelta = q->vdelta;}
+
+            S(x,y)->ceil = q->ceil;
+            S(x,y)->utex = q->utex;
+            S(x,y)->ctex = q->ctex;
+        }
+
+        q++;
+    }
+}

void freeblock(block *&b)
{

Linux and Mac ports needed.
Thanks given by:
#14
(13 Aug 14, 11:28PM)Felix-The-Ghost Wrote:
(14 Jun 14, 02:23AM)+f0r3v3r+ Wrote:
(13 Jun 14, 03:39PM)Music Wrote: Why don't you just release the source code?
^ This

Diff - based on 1.2.0.2 source

--- audiomanager.cpp    Tue Oct 22 13:57:19 2013
+++ audiomanager.cpp    Wed Apr 02 17:25:06 2014
@@ -554,7 +554,7 @@
// main audio update routine

void audiomanager::updateaudio()
-{
+{
     if(nosound) return;

     alcSuspendContext(context); // don't process sounds while we mess around
@@ -606,7 +606,7 @@
         {
             entity &e = ents[i];
             vec o(e.x, e.y, e.z);
-            if(e.type!=SOUND) continue;
+            if(e.type!=SOUND) continue;

             int sound = e.attr1;
             int radius = e.attr2;
--- clientgame.cpp    Tue Oct 22 13:57:19 2013
+++ clientgame.cpp    Fri Apr 11 14:43:34 2014
@@ -1116,10 +1116,13 @@
COMMAND(showmapstats, "");

VARP(showmodedescriptions, 0, 1, 1);
-extern bool canceldownloads;
+extern bool canceldownloads;
+
+extern bool lockselent;

void startmap(const char *name, bool reset)   // called just after a map load
-{
+{
+    lockselent = false;
     canceldownloads = false;
     copystring(clientmap, name);
     sendmapidenttoserver = true;
--- cube.h    Sat Nov 09 17:56:31 2013
+++ cube.h    Mon May 19 23:38:03 2014
@@ -43,7 +43,8 @@
extern vector<int> eh_ents;             // edithide entities
extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world
extern int lastmillis, totalmillis, nextmillis; // last time
-extern int curtime;                     // current frame time
+extern int curtime;                     // current frame time
+extern int globalfps;
extern int interm;
extern int gamemode, nextmode;
extern int gamespeed;
--- editing.cpp    Tue Oct 22 13:57:16 2013
+++ editing.cpp    Sat Jun 07 00:41:10 2014
@@ -3,12 +3,20 @@
#include "cube.h"

bool editmode = false;
+
+int globalfps = 0;

// the current selections, used by almost all editing commands
// invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map
// => selections are checked when they are made or when the world is reloaded

-vector<block> sels;
+VAR(cleanedit, 0, 0, 1);
+COMMANDF(togglecleanedit, "", () {cleanedit = !cleanedit;});
+
+COMMANDF(clearrecenttex, "", () {loopk(3) loopi(256) hdr.texlists[k][i] = i;});
+
+vector<block> sels;
+vector<block> s_sels; //saved selections

#define loopselxy(sel, b) { makeundo(sel); loop(x,(sel).xs) loop(y,(sel).ys) { sqr *s = S((sel).x+x, (sel).y+y); b; } remip(sel); }
#define loopselsxy(b) { loopv(sels) loopselxy(sels[i], b); }
@@ -70,6 +78,20 @@
     }
     return !editmode;
}
+
+void savesel()
+{
+    s_sels = sels;
+}
+
+void reselect()
+{
+    sels = s_sels;
+}
+
+COMMAND (savesel, "");
+COMMAND (reselect, "");
+

inline bool selset()
{
@@ -81,6 +103,48 @@
     if(!selset()) conoutf("no selection");
     return !selset();
}
+
+const char *entcolor(int index) // helper function for editinfo()
+{
+    entity &e = ents[index];
+
+    switch(e.type)
+    {
+        case 2: // playerstart
+        if (e.attr2 == 0) // CLA
+        return "3";
+
+        else if (e.attr2 == 1) // RVSF
+        return "1";
+
+        else return "J"; // FFA
+
+        case 3: case 4: case 5: case 9: return "9"; // pistol clip, ammobox, grenade, akimbo
+        case 6: case 7: case 8: return "H"; // health pack, helmet, armor
+        case 10: return "T"; // mapmodel
+
+        case 12: return "M"; // ladder
+
+        case 13: // ctf-flag
+        if (e.attr2 == 0) // CLA
+        return "3";
+
+        else if (e.attr2 == 1) // RVSF
+        return "1";
+        break;
+
+        case 14: return "P"; // sound
+
+        case 15: return "2"; // clip
+        case 16: return "X"; // plclip
+
+        default:
+        return "5"; // white
+    }
+    return "5"; // white
+}
+
+extern bool lockselent;

char *editinfo()
{
@@ -88,14 +152,43 @@
     if(!editmode) return NULL;
     int e = closestent();
     if(e<0) return NULL;
-    entity &c = ents[e];
+    entity &c = ents[e];
+
     string selinfo = "no selection";
-    if(selset()) formatstring(selinfo)("selection = (%d, %d)", (sels.last()).xs, (sels.last()).ys);
-    formatstring(info)("closest entity = %s (%d, %d, %d, %d), %s", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo);
+    if(selset()) formatstring(selinfo)("selection = (%d, %d)", (sels.last()).xs, (sels.last()).ys);
+
+    string fileinfo = "";
+    switch (c.type)
+    {
+        case SOUND:
+        if (mapsounds.inrange(c.attr1))
+        formatstring(fileinfo)("\n%s", mapsounds[c.attr1].buf->name);
+        else
+        formatstring(fileinfo)("\n\f7unregistered sound\f5");
+        break;
+
+        case MAPMODEL:
+        {
+            mapmodelinfo &mmi = getmminfo(c.attr2);
+            if (&mmi)
+            formatstring(fileinfo)("\n%s", mmi.name);
+            else
+            formatstring(fileinfo)("\n\f7unregistered mapmodel\f5");
+        }
+        break;
+
+        default:
+        break;
+    }
+
+    if (!lockselent)
+    formatstring(info)("closest entity:\n\f%s%s\f5 (%d, %d, %d, %d), %s %s", entcolor(e), entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo, fileinfo);
+    else
+    formatstring(info)("selected entity:\n\f%s%s\f5 (%d, %d, %d, %d), %s %s", entcolor(e), entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, selinfo, fileinfo);
+
     return info;
}

-
#define EDITSEL   if(noteditmode("EDITSEL") || noselection()) return
#define EDITSELMP if(noteditmode("EDITSELMP") || noselection() || multiplayer()) return
#define EDITMP    if(noteditmode("EDITMP") || multiplayer()) return
@@ -150,87 +243,182 @@

// VC8 optimizer screws up rendering somehow if this is an actual function
#define sheight(s,t,z) (!flrceil ? (s->type==FHF ? s->floor-t->vdelta/4.0f : (float)s->floor) : (s->type==CHF ? s->ceil+t->vdelta/4.0f : (float)s->ceil))
-
-void cursorupdate()                                     // called every frame from hud
-{
-    flrceil = ((int)(camera1->pitch>=0))*2;
-    int cyaw = ((int) camera1->yaw) % 180;
-    editaxis = editmode ? (fabs(camera1->pitch) > 65 ? 13 : (cyaw < 45 || cyaw > 135 ? 12 : 11)) : 0;
-
-    volatile float x = worldpos.x;                      // volatile needed to prevent msvc7 optimizer bug?
-    volatile float y = worldpos.y;
-    volatile float z = worldpos.z;
-
-    cx = (int)x;
-    cy = (int)y;
-
-    if(OUTBORD(cx, cy)) return;
-    sqr *s = S(cx,cy);
-
-    if(fabs(sheight(s,s,z)-z)>1)                        // selected wall
-    {
-        x += x>camera1->o.x ? 0.5f : -0.5f;             // find right wall cube
-        y += y>camera1->o.y ? 0.5f : -0.5f;
-
-        cx = (int)x;
-        cy = (int)y;
-
-        if(OUTBORD(cx, cy)) return;
-    }
-
-    if(dragging) { makesel(false); };
-
-    const int GRIDSIZE = 5;
-    const float GRIDW = 0.5f;
-    const float GRID8 = 2.0f;
-    const float GRIDS = 2.0f;
-    const int GRIDM = 0x7;
-
-    // render editing grid
-
-    if(showgrid)
-    {
-        for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++)
-        {
-
-            if(OUTBORD(ix, iy)) continue;
-            sqr *s = S(ix,iy);
-            if(SOLID(s)) continue;
-            float h1 = sheight(s, s, z);
-            float h2 = sheight(s, SWS(s,1,0,sfactor), z);
-            float h3 = sheight(s, SWS(s,1,1,sfactor), z);
-            float h4 = sheight(s, SWS(s,0,1,sfactor), z);
-            if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40);
-            else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80);
-            else linestyle(GRIDW, 0x80, 0x80, 0x80);
-            block b = { ix, iy, 1, 1 };
-            box(b, h1, h2, h3, h4);
-            linestyle(GRID8, 0x40, 0x40, 0xFF);
-            if(!(ix&GRIDM))   line(ix,   iy,   h1, ix,   iy+1, h4);
-            if(!((ix+1)&GRIDM)) line(ix+1, iy,   h2, ix+1, iy+1, h3);
-            if(!(iy&GRIDM))   line(ix,   iy,   h1, ix+1, iy,   h2);
-            if(!((iy+1)&GRIDM)) line(ix,   iy+1, h4, ix+1, iy+1, h3);
-        }
-
-        if(!SOLID(s))
-        {
-            float ih = sheight(s, s, z);
-            linestyle(GRIDS, 0xFF, 0xFF, 0xFF);
-            block b = { cx, cy, 1, 1 };
-            box(b, ih, sheight(s, SWS(s,1,0,sfactor), z), sheight(s, SWS(s,1,1,sfactor), z), sheight(s, SWS(s,0,1,sfactor), z));
-            linestyle(GRIDS, 0xFF, 0x00, 0x00);
-            dot(cx, cy, ih);
-            ch = (int)ih;
-        }
-    }
-
-    if(selset())
-    {
-        linestyle(GRIDS, 0xFF, 0x40, 0x40);
-        loopv(sels) box(sels[i], (float)sels[i].h, (float)sels[i].h, (float)sels[i].h, (float)sels[i].h);
-    }
-
-    glLineWidth(1);
+
+int gridalpha = 255;
+int delay = 128;
+float old_p, old_y;
+
+void cursorupdate()                                     // called every frame from hud
+{
+    if (old_p != camera1->pitch || old_y != camera1->yaw)
+    {gridalpha = 255; delay = 128;}
+    else
+    {
+        delay -= ((255 / globalfps) + 1 );
+        if (delay < 0) delay = 0;
+
+        if (!delay)
+        gridalpha -= ((gridalpha * 3 / globalfps) + 1 );
+
+        if (gridalpha < 0)
+        gridalpha = 0;
+    }
+
+    if (!gridalpha)
+    return;
+
+    old_p = camera1->pitch;
+    old_y = camera1->yaw;
+
+    flrceil = ((int)(camera1->pitch>=0))*2;
+    int cyaw = ((int) camera1->yaw) % 180;
+    editaxis = editmode ? (fabs(camera1->pitch) > 65 ? 13 : (cyaw < 45 || cyaw > 135 ? 12 : 11)) : 0;
+
+    volatile float x = worldpos.x;                      // volatile needed to prevent msvc7 optimizer bug?
+    volatile float y = worldpos.y;
+    volatile float z = worldpos.z;
+
+    cx = (int)x;
+    cy = (int)y;
+
+    if (OUTBORD(cx, cy)) return;
+    sqr *s = S(cx,cy);
+
+    if(fabs(sheight(s,s,z)-z)>1)                        // selected wall
+    {
+        x += x>camera1->o.x ? 0.5f : -0.5f;             // find right wall cube
+        y += y>camera1->o.y ? 0.5f : -0.5f;
+
+        cx = (int)x;
+        cy = (int)y;
+
+        if(OUTBORD(cx, cy)) return;
+    }
+
+    if(dragging) { makesel(false); };
+
+    const int GRIDSIZE = 5;
+    const float GRIDW = 0.5f;
+    const float GRID8 = 2.0f;
+    const float GRIDS = 2.0f;
+    const int GRIDM = 0x7;
+
+    // render editing grid
+
+    glEnable(GL_BLEND);
+    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++)
+    {
+        if(OUTBORD(ix, iy)) continue;
+        sqr *s = S(ix,iy);
+
+        //if(SOLID(s)) continue;
+
+        float h1 = sheight(s, s, z);
+        float h2 = sheight(s, SWS(s,1,0,sfactor), z);
+        float h3 = sheight(s, SWS(s,1,1,sfactor), z);
+        float h4 = sheight(s, SWS(s,0,1,sfactor), z);
+
+        /** "Tag" cube style **/
+
+        if(s->tag)
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x40, 0x40, 0xFF, (unsigned char)gridalpha);
+        }
+
+        /** Heightfield Style **/
+
+        else if(s->type==FHF || s->type==CHF)
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x80, 0xFF, 0x80, (unsigned char)gridalpha); //green
+        }
+
+        /** Solid cube style **/
+
+        else if(SOLID(s))
+        {
+            glLineWidth(GRID8);
+            glColor4ub(0xAB, 0x40, 0xFE, (unsigned char)gridalpha); //purple
+        }
+
+        /** Normal cube style **/
+
+        else
+        {
+            glLineWidth(GRIDW);
+            glColor4ub(0x80, 0x80, 0x80, (unsigned char)gridalpha); //grey
+        }
+
+        /** Actually drawing the grid **/
+
+        block b = { ix, iy, 1, 1 }; //set destination coordinates to draw to
+        box(b, h1, h2, h3, h4); //draw
+
+        /** The repeating 8 x 8 grid **/
+
+        /* Choosing color **/
+
+        int temp = 255 - ((255-gridalpha)*3/2); //the thicker blue appears more opaque than other lines
+        if (temp < 0) temp = 0;
+
+        glLineWidth(GRID8);
+        glColor4ub(0x40, 0x40, 0xFF, (unsigned char)temp); //blue
+
+        /* Drawing lines on this grid */
+
+        if(!(ix&GRIDM))
+        line(ix, iy, h1, ix, iy+1, h4);
+
+        if(!((ix+1)&GRIDM))
+        line(ix+1, iy, h2, ix+1, iy+1, h3);
+
+        if(!(iy&GRIDM))
+        line(ix, iy, h1, ix+1, iy, h2);
+
+        if(!((iy+1)&GRIDM))
+        line(ix, iy+1, h4, ix+1, iy+1, h3);
+    }
+
+    /** Draw white square around cube cursor is over **/
+
+
+    float ih = sheight(s, s, z);
+    {
+        glLineWidth(GRIDS);
+        glColor4ub(0xFF, 0xFF, 0xFF, (unsigned char)gridalpha); //white
+    }
+
+    block b = { cx, cy, 1, 1 };
+    box(b, ih, sheight(s, SWS(s,1,0,sfactor), z), sheight(s, SWS(s,1,1,sfactor), z), sheight(s, SWS(s,0,1,sfactor), z));
+
+    /** And the orienting little square in the corner **/
+
+    glLineWidth(GRIDS);
+    glColor4ub(0xFF, 0x00, 0x00, (unsigned char)gridalpha); //red
+    dot(cx, cy, ih);
+
+    ch = (int)ih; //updates height of selection box (the red boxes) to the correct height
+
+    /** Draw selection(s) **/
+
+    if(selset())
+    {
+        /* Choosing color */
+
+        glLineWidth(GRIDS);
+        glColor4ub(0xFF, 0x40, 0x40, (unsigned char)gridalpha); //red
+
+        /* Drawing */
+
+        loopv(sels) //for each selection
+        box(sels[i], (float)sels[i].h, (float)sels[i].h, (float)sels[i].h, (float)sels[i].h);
+    }
+
+    glDisable(GL_BLEND);
+    glLineWidth(1);
}

vector<block *> undos;                                  // unlimited undo
@@ -308,6 +496,44 @@
         remipmore(sel);
     }
}
+
+
+void paste_half()
+{
+    if(noteditmode("EDITSEL") || noselection()) return;
+
+    if(!copybuffers.length()) { conoutf("nothing to paste"); return; }
+
+    loopv(sels)
+    {
+        block &sel = sels[i];
+        int selx = sel.x;
+        int sely = sel.y;
+
+        loopvj(copybuffers)
+        {
+            block *copyblock = copybuffers[j];
+            int dx = copyblock->x - copybuffers[0]->x, dy = copyblock->y - copybuffers[0]->y;
+
+            sel.xs = copyblock->xs;
+            sel.ys = copyblock->ys;
+            sel.x = selx + dx;
+            sel.y = sely + dy;
+
+            if(!correctsel(sel) || sel.xs!=copyblock->xs || sel.ys!=copyblock->ys) { conoutf("incorrect selection"); return; }
+            makeundo(sel);
+
+            if (flrceil == 0)
+            halfblockpaste(*copyblock, sel.x, sel.y, true);
+            else
+            halfblockpaste(*copyblock, sel.x, sel.y, false);
+        }
+
+        remipmore(sel);
+    }
+}
+
+COMMAND(paste_half, "");

// Count the walls of type "type" contained in the current selection
void countwalls(int *type)
@@ -338,6 +564,46 @@
         }
     }
}
+
+int getcurtex(const char* pos, const char* x, const char* y); //prototype
+
+void tex2front(const char* pos)
+{
+    if (noselection())
+    return;
+
+    int t = -1; //type (position)
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 3;
+    else if (strcmp(pos, "CEIL") == 0) t = 2;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    if (t == 3) t = 1; //walls
+
+    int key = getcurtex(pos, "", "");
+    //conoutf("Key is texture slot %i", key);
+    //conoutf("Searching edit texture lists for match...");
+
+    loopi(255)
+    {
+        if (hdr.texlists[t][i] == key)
+        {
+            //conoutf("Match found in position: %i", i);
+            curedittex[t] = i;
+            break;
+        }
+    }
+
+    tofronttex();
+}
+
+COMMAND (tex2front, "s");

void editdrag(bool isdown)
{
@@ -454,6 +720,1002 @@
         addmsg(SV_EDITT, "ri6", sels[i].x, sels[i].y, sels[i].xs, sels[i].ys, type, t);
     }
}
+
+int expandsel(int s) //makes all selections collections of 1 x 1 selections
+{
+    int count = 0; //number of cubes in selections to remove
+
+    int stop = sels.length();
+    int removed = 0;
+
+    for (int i = 0; i < stop; i++)
+    {
+        for (int y = sels[i].y; y < sels[i].y + sels[i].ys; y++) //y loop
+        for (int x = sels[i].x; x < sels[i].x + sels[i].xs; x++) //x loop
+        {
+            addselection(x, y, 1, 1, sels[i].h);
+
+            if (i >= stop - s) //selections to remove
+            count++;
+        }
+
+        sels.remove(i);
+        i--;
+        stop--;
+        removed++;
+    }
+
+
+    return count;
+}
+
+void compress_sel(int s) //combines adjacent selections (1 x 1 selections only)
+{
+    EDITSEL;
+
+    int min_x = 0;
+    int min_y = 0;
+
+    int max_x = 0;
+    int max_y = 0;
+    int max_h = 0;
+
+    int x;
+    int y;
+    int h;
+
+    /** Initialize selections map **/
+
+    loopv(sels)
+    {
+        if (i == 0) //initialize bounds
+        {
+            min_x = sels[i].x + sels[i].xs;
+            max_x = sels[i].x + sels[i].xs;
+            min_y = sels[i].y + sels[i].ys;
+            max_y = sels[i].y + sels[i].ys;
+            max_h = sels[i].h;
+        }
+
+        x = sels[i].x + sels[i].xs;
+        y = sels[i].y + sels[i].ys;
+        h = sels[i].h;
+
+        if (x < min_x) min_x = x;
+
+        if (x > max_x) max_x = x;
+
+        if (y < min_y) min_y = y;
+
+        if (y > max_y) max_y = y;
+
+        if (h > max_h) max_h = h;
+    }
+
+    bool cubemap [2 + (max_y - min_y)][2 + (max_x - min_x)];
+    memset(cubemap, false, sizeof(cubemap));
+
+    for(int i = 0; i<sels.length(); i++)
+    {
+        if (i < sels.length()-s)
+        cubemap[sels[i].y + sels[i].ys - min_y] [sels[i].x + sels[i].xs - min_x] = true;
+        else
+        cubemap[sels[i].y + sels[i].ys - min_y] [sels[i].x + sels[i].xs - min_x] = false;
+    }
+
+    /** reselect rows of cubes according to map **/
+
+    resetselections();
+
+    int slice_start; //first cube in a horizontal selection slice
+
+    for (y = 0; y < (max_y - min_y) + 1; y++)
+    {
+        slice_start = -1;
+
+        for (x = 0; x < (max_x - min_x) + 1; x++)
+        {
+            if (cubemap[y][x])
+            {
+                if (slice_start < 0)
+                slice_start = x;
+            }
+            else
+            {
+                if (slice_start > -1)
+                addselection(min_x + slice_start - 1, min_y + y - 1, x - slice_start, 1, max_h);
+
+                slice_start = -1;
+                continue;
+            }
+
+            if ((x + 1 >= (max_x - min_x) + 1) && slice_start > -1)
+            addselection(min_x + slice_start - 1, min_y + y - 1, x - slice_start + 1, 1, max_h);
+        }
+    }
+
+    /** combine rows of equal length vertically adjacent to each other **/
+
+    int y2;
+
+    loopv (sels) //i loop
+    loopvj (sels) //j loop
+    {
+        if (sels[i].x == sels[j].x) //same x coordinate
+        if (sels[i].y == sels[j].y + sels[j].ys || sels[j].y == sels[i].y + sels[i].ys) //vertically adjacent
+        if (sels[i].xs == sels[j].xs) //same length
+        {
+            min_y = sels[i].y;
+            max_y = sels[i].y + sels[i].ys;
+            max_h = sels[i].h;
+
+            y = sels[j].y;
+            y2 = sels[j].y + sels[j].ys;
+            h = sels[j].h;
+
+            if (y < min_y) min_y = y;
+            if (y > max_y) max_y = y;
+            if (y2 < min_y) min_y = y2;
+            if (y2 > max_y) max_y = y2;
+            if (h > max_h) max_h = h;
+
+            addselection(sels[i].x, min_y, sels[i].xs, max_y - min_y, max_h);
+
+            sels.remove(i);
+
+            if (i < j)  //if deleting i offset j
+            sels.remove(j - 1);
+            else
+            sels.remove(j);
+
+            i = 0;
+            break; //exit this j loop, go to next j loop, of i loop
+        }
+    }
+}
+
+void fixselh ()
+{
+    bool fc;
+
+    if (flrceil == 0) fc = false; //floor
+    else fc = true;
+
+    sqr *s1;
+    sqr *s2;
+
+    loopv (sels)
+    {
+        s1 = S(sels[i].x, sels[i].y);
+        s2 = S(sels[i].x + sels[i].xs - 1, sels[i].y + sels[i].ys - 1);
+
+        if(!fc) //finding max floor height
+        {
+            if (s1->floor > s2->floor)
+            sels[i].h = s1->floor;
+            else
+            sels[i].h = s2->floor;
+        }
+        else //finding max ceiling height
+        {
+            if (s1->ceil > s2->ceil)
+            sels[i].h = s1->ceil;
+            else
+            sels[i].h = s2->ceil;
+        }
+    }
+}
+
+/** expand selections and select only selections matching argument keys **/
+
+void filter_sel(const char* type, char* v1, char* v2, char* v3, char* v4, char* v5, char* v6, char* v7)
+{
+    EDITSEL;
+
+    /** Process filter type **/
+
+    int filter_type = 0;
+
+    if (strcmp(type, "FLOOR_T") == 0) filter_type = 1;
+    if (strcmp(type, "WALL_T") == 0) filter_type = 2;
+    if (strcmp(type, "UPWALL_T") == 0) filter_type = 3;
+    if (strcmp(type, "CEIL_T") == 0) filter_type = 4;
+    if (strcmp(type, "FLOOR_H") == 0) filter_type = 5;
+    if (strcmp(type, "CEIL_H") == 0) filter_type = 6;
+    if (strcmp(type, "DISSOLVE") == 0) filter_type = 7;
+    if (strcmp(type, "SOLID") == 0) filter_type = 8;
+    if (strcmp(type, "HEIGHTFIELD") == 0) filter_type = 9;
+    if (strcmp(type, "CORNER") == 0) filter_type = 10;
+    if (strcmp(type, "SPACE") == 0) filter_type = 11;
+    if (strcmp(type, "SUBTRACT") == 0) filter_type = 12;
+    if (strcmp(type, "MERGE") == 0) filter_type = 13;
+
+    if (filter_type == 0)
+    {
+        conoutf("\f9ERROR: \f5invalid filter type");
+        return;
+    }
+
+    /** Count value arguments **/
+
+    int num_v = 0;
+
+    if (strcmp(v1, "") != 0) num_v++;
+
+    if (strcmp(v2, "") != 0) num_v++;
+
+    if (strcmp(v3, "") != 0) num_v++;
+
+    if (strcmp(v4, "") != 0) num_v++;
+
+    if (strcmp(v5, "") != 0) num_v++;
+
+    if (strcmp(v6, "") != 0) num_v++;
+
+    if (strcmp(v7, "") != 0) num_v++;
+
+    if (num_v < 1 && filter_type != 12 && filter_type != 13)
+    {
+        conoutf("\f9ERROR: \f5this filter requires at least one key value");
+        return;
+    }
+
+    int v [num_v];
+
+    if (num_v > 0) v[0] = atoi(v1);
+    if (num_v > 1) v[1] = atoi(v2);
+    if (num_v > 2) v[2] = atoi(v3);
+    if (num_v > 3) v[3] = atoi(v4);
+    if (num_v > 4) v[4] = atoi(v5);
+    if (num_v > 5) v[5] = atoi(v6);
+    if (num_v > 6) v[6] = atoi(v7);
+
+    int count = 0;
+
+    if (strcmp(v1, "") != 0)
+    count = expandsel(v[0]); //expand selections to 1 x 1 selections
+    else
+    count = expandsel(1);
+
+    /** filter selection(s) */
+
+    bool match; //match found
+
+    loopv(sels)
+    {
+        sqr *s = S((sels[i]).x, (sels[i]).y);
+        match = false;
+
+        switch(filter_type)
+        {
+            case 1: //floor texture
+            loopj (num_v)
+            {
+                if (s->ftex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 2: //wall texture
+            loopj (num_v)
+            {
+                if (s->wtex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 3: // upper wall texture
+            loopj (num_v)
+            {
+                if (s->utex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 4: //ceiling texture
+            loopj (num_v)
+            {
+                if (s->ctex == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 5: //floor height
+            loopj (num_v)
+            {
+                if (s->floor == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 6: //ceiling height
+            loopj (num_v)
+            {
+                if (s->ceil == v[j])
+                {
+                    match = true;
+                    break;
+                }
+            }
+
+            //no match by end of iteration
+
+            if (!match)
+            {
+                sels.remove(i);
+                i--;
+            }
+
+            break;
+
+            case 7: //dissolve
+            if (1 + rand()%99 < v[0]) //frequency
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 8: //solid
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == SOLID)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != SOLID)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 9: //heightfield
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == FHF || s->type == CHF)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != FHF && s->type != CHF)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 10: //corner
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == CORNER)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != CORNER)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            case 11: //space
+            if (v[0] < 1) //if / if not
+            {
+                if (s->type == SPACE)
+                {
+                    sels.remove(i);
+                    i--;
+                }
+            }
+            else if (s->type != SPACE)
+            {
+                sels.remove(i);
+                i--;
+            }
+            break;
+
+            default:
+            break;
+        }
+    }
+
+    if (!sels.empty()) //if there is a selection to simplify
+    {
+        if (filter_type == 12 && count > 0) //remove
+        compress_sel(count);
+        else
+        compress_sel(0);
+    }
+
+    fixselh();
+}
+
+COMMAND (filter_sel, "ssssssss");
+
+extern float Mh;
+
+void select_all()
+{
+    resetselections();
+    addselection(mapdims[0] - 1, mapdims[1] - 1, mapdims[2] - mapdims[0] + 3, mapdims[3] - mapdims[1] + 3, Mh);
+}
+
+COMMAND (select_all, "");
+
+struct Slot
+{
+    string name;
+    float scale;
+    Texture *tex;
+    bool loaded;
+};
+
+extern vector<Slot> slots;
+
+void texname(const char* pos)
+{
+    if (noselection())
+    {
+        result("first make a selection");
+        return;
+    }
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-1");
+        return;
+    }
+
+    s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+
+    if (!s)
+    {
+        result("error");
+        return;
+    }
+
+    switch (t)
+    {
+        case 0:
+        result(slots[s->ftex].tex->name);
+        break;
+
+        case 1:
+        result(slots[s->wtex].tex->name);
+        break;
+
+        case 2:
+        result(slots[s->utex].tex->name);
+        break;
+
+        case 3:
+        result(slots[s->ctex].tex->name);
+        break;
+
+        default:
+        result("error");
+    }
+}
+
+COMMAND(texname, "s");
+
+int getcurtex(const char* pos, const char* x, const char* y) //returns index of texture slot
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args == 1) //working from selection
+    {
+        if (noselection())
+        {
+            result("-1");
+            return -1;
+        }
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        result("-1");
+        return -1;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-1");
+        return -1;
+    }
+
+    /**/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            result("-1");
+            return -1;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        result("-1");
+        return -1;
+    }
+
+    string buf = "";
+
+    switch (t)
+    {
+        case 0:
+        concatformatstring(buf, "%d ", s->ftex);
+        result(buf);
+        return s->ftex;
+
+        case 1:
+        concatformatstring(buf, "%d ", s->wtex);
+        result(buf);
+        return s->wtex;
+
+        case 2:
+        concatformatstring(buf, "%d ", s->utex);
+        result(buf);
+        return s->utex;
+
+        case 3:
+        concatformatstring(buf, "%d ", s->ctex);
+        result(buf);
+        return s->ctex;
+
+        default:
+        result("-1");
+        return -1;
+    }
+
+    result("-1");
+    return -1;
+}
+
+void curtex(const char* pos, const char* x, const char* y) //prints texture slot info
+{
+    /** Count arguments **/
+
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        return;
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        return;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int t = -1; //type
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) t = 0;
+    else if (strcmp(pos, "WALL") == 0) t = 1;
+    else if (strcmp(pos, "UPWALL") == 0) t = 2;
+    else if (strcmp(pos, "CEIL") == 0) t = 3;
+    else if (strcmp(pos, "ALL") == 0 || num_args == 0) t = 4;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    /**/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 0:
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            return;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        return;
+    }
+
+    switch (t)
+    {
+        case 0:
+        conoutf("The \fPfloor \f5texture of this cube is slot number \fN%i", s->ftex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 1:
+        conoutf("The \fPwall \f5texture of this cube is slot number \fN%i", s->wtex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 2:
+        conoutf("The \fPupper wall \f5texture of this cube is slot number \fN%i", s->utex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 3:
+        conoutf("The \fPceiling \f5texture of this cube is slot number \fN%i", s->ctex);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 4:
+        conoutf("The \fPfloor \f5texture of this cube is slot number \fN%i", s->ftex);
+        conoutf("The \fPwall \f5texture of this cube is slot number \fN%i", s->wtex);
+        conoutf("The \fPupper wall \f5texture of this cube is slot number \fN%i", s->utex);
+        conoutf("The \fPceiling \f5texture of this cube is slot number \fN%i", s->ctex);
+        conoutf("\fM--------------------------------------------------------------");
+
+        default:
+        return;
+    }
+}
+
+COMMAND(curtex, "sss");
+COMMAND(getcurtex, "sss");
+
+int getcurheight(const char* pos, const char* x, const char* y) //returns height of cubes
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        {
+            result("-255");
+            return -255;
+        }
+
+        if (num_args == 0)
+        {
+            conoutf("\f9ERROR: \f5getcurheight cannot be called with no arguments");
+            result("-255");
+            return -255;
+        }
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        result("-255");
+        return -255;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int p = -1; //position
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) p = 0;
+    else if (strcmp(pos, "CEIL") == 0) p = 1;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        result("-255");
+        return -255;
+    }
+
+    /** Grab cube **/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            result("-255");
+            return -255;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        result("-255");
+        return -255;
+    }
+
+    /** get height **/
+
+    string buf = "";
+
+    switch (p)
+    {
+        case 0:
+        concatformatstring(buf, "%d ", s->floor);
+        result(buf);
+        return s->floor;
+
+        case 1:
+        concatformatstring(buf, "%d ", s->ceil);
+        result(buf);
+        return s->ceil;
+
+        default:
+        result("-255");
+        return -255;
+    }
+
+    result("-255");
+    return -255;
+}
+
+void curheight(const char* pos, const char* x, const char* y) //prints height of cubes
+{
+    /** Count arguments **/
+    int num_args = 0;
+
+    if (strcmp(pos, "") != 0)
+    num_args++;
+
+    if (strcmp(x, "") != 0)
+    num_args++;
+
+    if (strcmp(y, "") != 0)
+    num_args++;
+
+    if (num_args < 2) //working from selection
+    {
+        if (noselection())
+        return;
+    }
+
+    if (num_args == 2)
+    {
+        conoutf("\f9ERROR: \f5missing Y argument");
+        return;
+    }
+
+    /**/
+
+    sqr *s = 0; //cube
+    int p = -1; //position
+
+    /** get type to check **/
+
+    if (strcmp(pos, "FLOOR") == 0) p = 0;
+    else if (strcmp(pos, "CEIL") == 0) p = 1;
+    else if (num_args == 0 || strcmp(pos, "BOTH") == 0) p = 2;
+    else
+    {
+        conoutf("\f9ERROR: \f5invalid position argument");
+        return;
+    }
+
+    /** Grab cube **/
+
+    int X = atoi(x);
+    int Y = atoi(y);
+
+    switch (num_args)
+    {
+        case 0:
+        case 1:
+        s = S((sels[sels.length() - 1]).x, (sels[sels.length() - 1]).y);
+        break;
+
+        case 3:
+        if (OUTBORD(X, Y))
+        {
+            conoutf("\f9ERROR: \f5coordinates out of bounds");
+            return;
+        }
+        s = S(X, Y);
+        break;
+
+        default:
+        return;
+    }
+
+    /** get height **/
+
+    switch (p)
+    {
+        case 0:
+        conoutf("The \fPfloor \f5height of this cube is \fN%i", s->floor);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 1:
+        conoutf("The \fPceiling \f5height of this cube is \fN%i", s->ceil);
+        conoutf("\fM--------------------------------------------------------------");
+        break;
+
+        case 2:
+        conoutf("The \fPfloor \f5height of this cube is \fN%i", s->floor);
+        conoutf("The \fPceiling \f5height of this cube is \fN%i", s->ceil);
+        conoutf("\fM--------------------------------------------------------------");
+
+        default:
+        return;
+    }
+}
+
+COMMAND(getcurheight, "sss");
+COMMAND(curheight, "sss");
+
+void undo_sel(char* n) //removes last selection
+{
+    if (sels.empty())
+    {
+        conoutf("no selections to remove");
+        return;
+    }
+
+    int num;
+
+    if (strcmp(n, "") != 0) num = atoi(n);
+    else
+    num = 1;
+
+    while (num > 0)
+    {
+        if (sels.empty())
+        {
+            conoutf("no more selections to remove");
+            return;
+        }
+
+        sels.pop();
+        num--;
+    }
+}
+
+COMMAND (undo_sel, "s");

void replace()
{
@@ -528,6 +1790,42 @@
}

COMMAND(equalize, "i");
+
+void equalize_sels(int *flr) //equalizes *all* sels instead of *each* sel
+{
+    bool isfloor = *flr==0;
+    EDITSEL;
+
+    int hi = 0;
+    int low = 0;
+
+    loopv(sels) //collect min/max info
+    {
+        block &sel = sels[i];
+
+        loopselxy(sel,
+        {
+            if(s->floor<low) low = s->floor;
+            if(s->ceil>hi) hi = s->ceil;
+        });
+    }
+
+    loopv(sels) //height asignments
+    {
+        block &sel = sels[i];
+
+        loopselxy(sel,
+        {
+            if(isfloor) s->floor = low; else s->ceil = hi;
+            if(s->floor>=s->ceil) s->floor = s->ceil-1;
+        });
+
+        addmsg(SV_EDITE, "ri5", sel.x, sel.y, sel.xs, sel.ys, isfloor);
+    }
+
+}
+
+COMMAND(equalize_sels, "i");

void setvdeltaxy(int delta, block &sel)
{
--- entities.cpp    Tue Oct 22 13:57:16 2013
+++ entities.cpp    Fri May 23 02:52:06 2014
@@ -3,7 +3,9 @@
#include "cube.h"

VAR(showclips, 0, 1, 1);
-VAR(showmodelclipping, 0, 0, 1);
+VAR(showmodelclipping, 0, 0, 1);
+
+extern bool cleanedit;

vector<entity> ents;
vector<int> eh_ents; // edithide entities
@@ -26,7 +28,10 @@
  }

void renderclip(entity &e)
-{
+{
+    if (cleanedit)
+    return;
+
     float xradius = max(float(e.attr2), 0.1f), yradius = max(float(e.attr3), 0.1f);
     vec bbmin(e.x - xradius, e.y - yradius, float(S(e.x, e.y)->floor+e.attr1)),
         bbmax(e.x + xradius, e.y + yradius, bbmin.z + max(float(e.attr4), 0.1f));
@@ -124,7 +129,10 @@
COMMAND(seteditshow, "s");

void renderentarrow(const entity &e, const vec &dir, float radius)
-{
+{
+    if (cleanedit)
+    return;
+
     if(radius <= 0) return;
     float arrowsize = min(radius/8, 0.5f);
     vec epos(e.x, e.y, e.z);
@@ -153,11 +161,13 @@
     glEnable(GL_CULL_FACE);
     glEnable(GL_TEXTURE_2D);
}
+
+extern int globalfps;

void renderentities()
{
     int closest = editmode ? closestent() : -1;
-    if(editmode && !reflecting && !refracting && !stenciling)
+    if(editmode && !reflecting && !refracting && !stenciling && !cleanedit)
     {
         static int lastsparkle = 0;
         if(lastmillis - lastsparkle >= 20)
@@ -174,29 +184,49 @@
                 vec v(e.x, e.y, e.z);
                 if(vec(v).sub(camera1->o).dot(camdir) < 0) continue;
                 //particle_splash(i == closest ? PART_ELIGHT : PART_ECLOSEST, 2, 40, v);
-                int sc = PART_ECARROT; // "carrot" (orange) - entity slot currently unused, possibly "reserved"
+                int sc = PART_ELIGHT;
                 if(i==closest)
                 {
-                    sc = PART_ECLOSEST; // blue
-                }
-                else switch(e.type)
+                    sc = PART_ECLOSEST; // smoke
+                    particle_splash(PART_ECLOSEST, 1, 80, v);
+                }
+
+                switch(e.type)
                 {
-                    case LIGHT : sc = PART_ELIGHT; break; // white
-                    case PLAYERSTART: sc = PART_ESPAWN; break; // green
+                    case LIGHT : sc = PART_ELIGHT; break; // white
+
+                    case PLAYERSTART:
+                    if (e.attr2 == 0)
+                    {sc = PART_RED; break;} // CLA
+                    else if (e.attr2 == 1)
+                    {sc = PART_BLUE; break;} // RVSF
+                    else
+                    sc = PART_GREEN; break; // FFA
+
                     case I_CLIPS:
-                    case I_AMMO:
-                    case I_GRENADE: sc = PART_EAMMO; break; // red
+                    case I_AMMO:
+                    case I_AKIMBO:
+                    case I_GRENADE: sc = PART_EAMMO; break; // orange
                     case I_HEALTH:
                     case I_HELMET:
-                    case I_ARMOUR:
-                    case I_AKIMBO: sc = PART_EPICKUP; break; // yellow
-                    case MAPMODEL:
-                    case SOUND: sc = PART_EMODEL; break; // magenta
-                    case LADDER:
-                    case CLIP:
-                    case PLCLIP: sc = PART_ELADDER; break; // grey
-                    case CTF_FLAG: sc = PART_EFLAG; break; // turquoise
-                    default: break;
+                    case I_ARMOUR: sc = PART_EPICKUP; break; // yellow
+                    case MAPMODEL: sc = PART_EMODEL; break; // magenta
+                    case LADDER: sc = PART_ELADDER; break; // grey
+
+                    case CLIP: sc = PART_CLIP; break; // yellow (color of the clip)
+                    case PLCLIP: sc = PART_PLCLIP; break; // magenta (color of the clip)
+
+                    case CTF_FLAG:
+                    if (e.attr2 == 0) // CLA
+                    {sc = PART_RED; break;}
+                    else if (e.attr2 == 1) // RVSF
+                    {sc = PART_BLUE; break;}
+                    else
+                    {sc = PART_ELIGHT; break;}
+
+                    default: break;
+
+                    case SOUND: sc = 22; break; // cyan
                 }
                 //particle_splash(sc, i==closest?6:2, i==closest?120:40, v);
                 particle_splash(sc, 2, 40, v);
@@ -240,8 +270,14 @@
             switch(e.type)
             {
                 case PLAYERSTART:
-                {
-                    glColor3f(0, 1, 1);
+                {
+                    if (e.attr2 == 0)
+                    glColor3f(1, 0, 0); // CLA
+                    else if (e.attr2 == 1)
+                    glColor3f(0, 0, 1); // RVSF
+                    else
+                    glColor3f(0, 1, 0); // FFA
+
                     vec dir;
                     vecfromyawpitch(e.attr1, 0, -1, 0, dir);
                     renderentarrow(e, dir, 4);
--- main.cpp    Tue Oct 29 04:33:15 2013
+++ main.cpp    Mon May 19 23:38:43 2014
@@ -1280,7 +1280,9 @@
         serverslice(0);

         if(elapsed) fps = (1000.0f/elapsed+fps*10)/11; // avoid DIV-by-0
-        frames++;
+        frames++;
+
+        globalfps = fps;

         audiomgr.updateaudio();

@@ -1322,4 +1324,4 @@
}

VAR(version, 1, AC_VERSION, 0);
-VAR(protocol, 1, PROTOCOL_VERSION, 0);
\ No newline at end of file
+VAR(protocol, 1, PROTOCOL_VERSION, 0);
--- Makefile    Sat Nov 09 18:12:11 2013
+++ Makefile    Wed Apr 16 20:37:19 2014
@@ -1,6 +1,12 @@
-CXXFLAGS= -O3 -fomit-frame-pointer
-CXX=clang++    # Use clang++, as g++ optimizations cause crashes...
-override CXXFLAGS+= -Wall -fsigned-char
+
+
+CXXFLAGS= -O3 -fomit-frame-pointer -Wno-unused-variable
+ifneq (,$(findstring MINGW,$(PLATFORM)))
+  CXXFLAGS+= -Wall -fsigned-char
+else
+  CXX=clang++   # Use clang++, as g++ optimizations cause crashes...
+  override CXXFLAGS+= -Wall -fsigned-char
+endif

PLATFORM= $(shell uname -s)
PLATFORM_PREFIX=native
@@ -392,3 +398,4 @@
master-standalone.o: cube.h platform.h tools.h geom.h model.h protocol.h
master-standalone.o: sound.h weapon.h entity.h world.h i18n.h command.h
master-standalone.o: varray.h vote.h console.h protos.h
+
--- physics.cpp    Tue Oct 22 13:57:19 2013
+++ physics.cpp    Thu May 22 20:37:00 2014
@@ -474,7 +474,7 @@
                     }
                 }

-                if(timeinair > 200 && !pl->timeinair)
+                if(timeinair > 200 && !pl->timeinair && !water)
                 {
                     int sound = timeinair > 800 ? S_HARDLAND : S_SOFTLAND;
                     if(pl->state!=CS_DEAD)
--- protos.h    Sat Nov 09 13:48:58 2013
+++ protos.h    Fri May 23 02:52:10 2014
@@ -496,7 +496,8 @@
extern void removedynlights(physent *owner);
extern block *blockcopy(const block &b);
extern void blockpaste(const block &b, int bx, int by, bool light);
-extern void blockpaste(const block &b);
+extern void blockpaste(const block &b);
+extern void halfblockpaste(const block &b, int bx, int by, bool floor);
extern void freeblock(block *&b);

// worldrender
@@ -626,7 +627,7 @@
{
     PART_SPARK = 0,
     PART_SMOKE,
-    PART_ECLOSEST,
+    PART_BLUE,
     PART_BLOOD,
     PART_DEMOTRACK,
     PART_FIREBALL,
@@ -637,13 +638,16 @@
     PART_HUDMUZZLEFLASH,
     PART_MUZZLEFLASH,
     PART_ELIGHT,
-    PART_ESPAWN,
-    PART_EAMMO,
+    PART_GREEN,
+    PART_RED,
     PART_EPICKUP,
     PART_EMODEL,
-    PART_ECARROT,
+    PART_EAMMO,
     PART_ELADDER,
-    PART_EFLAG
+    PART_ECLOSEST,
+    PART_SOUND = 22,
+    PART_CLIP,
+    PART_PLCLIP
};

extern void particleinit();
--- renderhud.cpp    Tue Oct 22 13:57:19 2013
+++ renderhud.cpp    Sat Jun 07 00:20:51 2014
@@ -1,6 +1,8 @@
// renderhud.cpp: HUD rendering

#include "cube.h"
+
+extern bool cleanedit;

void drawicon(Texture *tex, float x, float y, float s, int col, int row, float ts)
{
@@ -278,7 +280,10 @@
COMMAND(loadcrosshair, "ss");

void drawcrosshair(playerent *p, int n, color *c, float size)
-{
+{
+    if (cleanedit && editmode)
+    return;
+
     Texture *crosshair = crosshairs[n];
     if(!crosshair)
     {
@@ -890,7 +895,22 @@
     char *infostr = editinfo();
     int commandh = 1570 + FONTH;
     if(command) commandh -= rendercommand(20, 1570, VIRTW);
-    else if(infostr) draw_text(infostr, 20, 1570);
+
+    else if(infostr)
+    {
+        if (cleanedit)
+        {
+            glPushMatrix();
+            glLoadIdentity();
+            glOrtho(0, VIRTW*2, VIRTH*2, 0, -1, 1);
+            glScalef(1.0, 1.0, 1.0); //set scale
+            draw_text(infostr, 48, VIRTH*2 -4*FONTH);
+            glPopMatrix();
+        }
+        else
+        draw_text(infostr, 20, 1570);
+    }
+
     else if(targetplayer && showtargetname) draw_text(colorname(targetplayer), 20, 1570);
     glLoadIdentity();
     glOrtho(0, VIRTW*2, VIRTH*2, 0, -1, 1);
--- renderparticles.cpp    Tue Oct 22 13:57:19 2013
+++ renderparticles.cpp    Fri May 23 02:58:31 2014
@@ -184,7 +184,7 @@
     }
}

-#define MAXPARTYPES 22
+#define MAXPARTYPES 25

struct particle { vec o, d; int fade, type; int millis; particle *next; };
particle *parlist[MAXPARTYPES], *parempty = NULL;
@@ -252,7 +252,7 @@
{
     { PT_PART,       0.4f, 0.4f, 0.4f, 2,  0, 0.06f }, // yellow: sparks
     { PT_PART,       1.0f, 1.0f, 1.0f, 20, 1, 0.15f }, // grey:   small smoke
-    { PT_PART,       0.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // blue:   edit mode closest ent
+    { PT_PART,       0.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // blue:   used for RVSF related entities
     { PT_BLOOD,      0.5f, 0.0f, 0.0f, 1,  4, 0.3f  }, // red:    blood spats
     { PT_PART,       1.0f, 0.1f, 0.1f, 0,  1, 0.2f  }, // red:    demotrack
     { PT_FIREBALL,   1.0f, 0.5f, 0.5f, 0,  2, 7.0f  }, // explosion fireball
@@ -264,16 +264,19 @@
     { PT_HUDFLASH,   1.0f, 1.0f, 1.0f, 0,  6, 0.7f  }, // hudgun muzzle flash
     { PT_FLASH,      1.0f, 1.0f, 1.0f, 0,  6, 0.7f  }, // muzzle flash
     { PT_PART,       1.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // white: edit mode ent type : light
-    { PT_PART,       0.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // green: edit mode ent type : spawn
-    { PT_PART,       1.0f, 0.0f, 0.0f, 20, 0, 0.08f }, // red: edit mode ent type : ammo
-    { PT_PART,       1.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // yellow: edit mode ent type : pickup
-    { PT_PART,       1.0f, 0.0f, 1.0f, 20, 0, 0.08f }, // magenta: edit mode ent type : model, sound
-    { PT_PART,       1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode ent type : "carrot"
-    { PT_PART,       0.5f, 0.5f, 0.5f, 20, 0, 0.08f }, // grey: edit mode ent type : ladder, (pl)clip
-    { PT_PART,       0.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // turquoise: edit mode ent type : CTF-flag
+    { PT_PART,       0.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // green: edit mode ent type : FFA spawn
+    { PT_PART,       1.0f, 0.0f, 0.0f, 20, 0, 0.08f }, // red: used for CLA related entities
+    { PT_PART,       0.78f, 1.0f, 0.33f, 20, 0, 0.08f }, // yellow-green: edit mode ent type : pickup
+    { PT_PART,       0.6f, 0.3f, 0.9f, 20, 0, 0.08f }, // purple: edit mode ent type : mapmodel
+    { PT_PART,       1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode ent type : ammo
+    { PT_PART,       0.7f, 1.0f, 1.0f, 20, 0, 0.08f }, // bright cyan: edit mode ent type : ladder,
+    { PT_PART,       0.45f, 0.45f, 0.45f, 20, 1, 0.2f }, // smoke effect: closest/selected entity
     // 2011jun18 : shotty decals
     { PT_BULLETHOLE, 0.2f, 0.2f, 1.0f, 0,  3, 0.1f  }, // hole decal M
-    { PT_BULLETHOLE, 0.2f, 1.0f, 0.2f, 0,  3, 0.1f  }, // hole decal C
+    { PT_BULLETHOLE, 0.2f, 1.0f, 0.2f, 0,  3, 0.1f  }, // hole decal C
+    { PT_PART,       0.0f, 1.0f, 1.0f, 20, 0, 0.08f }, // cyan: edit mode ent type: sound
+    { PT_PART,       1.0f, 1.0f, 0.0f, 20, 0, 0.08f }, // yellow: edit mode ent type: clip
+    { PT_PART,       1.0f, 0.0f, 1.0f, 20, 0, 0.08f } // magenta: edit mode ent type: plclip
};

VAR(particlesize, 20, 100, 500);
--- tools.h    Tue Oct 22 13:57:19 2013
+++ tools.h    Wed Apr 02 10:50:08 2014
@@ -424,7 +424,7 @@

     T &pop() { return buf[--ulen]; }
     T &last() { return buf[ulen-1]; }
-    void drop() { buf[--ulen].~T(); }
+    void drop() { buf[--ulen].~T();}
     bool empty() const { return ulen==0; }

     int capacity() const { return alen; }
--- world.cpp    Tue Oct 22 13:57:19 2013
+++ world.cpp    Tue May 20 19:12:15 2014
@@ -127,10 +127,24 @@

COMMAND(nextclosestent, "");
COMMAND(closestenttype, "s");
+
+int active_ent = -1;
+bool locking_ent = false, lockselent = false;
+
+COMMANDF(togglelockselent, "", (void) { active_ent = -1; lockselent = !lockselent; if (lockselent) locking_ent = true;});

int closestent()        // used for delent and edit mode ent display
{
-    if(noteditmode("closestent")) return -1;
+    if(noteditmode("closestent")) return -1;
+
+    if (lockselent && !locking_ent && active_ent != -1)
+    {
+        if (ents[active_ent].type != NOTUSED)
+        return active_ent;
+        else
+        lockselent = false;
+    }
+
     int best = -1, bcnt = 0;
     float bdist = 99999;
     loopj(3)
@@ -150,7 +164,15 @@
             {
                 if(ents[best].x == e.x && ents[best].y == e.y && ents[best].z == e.z)
                 {
-                    if(j == 2 && bcnt == clentsel) return i;
+                    if(j == 2 && bcnt == clentsel)
+                    {
+                        if (locking_ent)
+                        {
+                            active_ent = i;
+                            locking_ent = false;
+                        }
+                        return i;
+                    }
                     bcnt++;
                 }
             }
@@ -162,7 +184,14 @@
         }
         if(best < 0 || bcnt == 1) break;
         if(bcnt) clentsel %= bcnt;
-    }
+    }
+
+    if (locking_ent)
+    {
+        active_ent = best;
+        locking_ent = false;
+    }
+
     return best;
}

@@ -173,7 +202,16 @@
     entity &e = ents[n];
     switch(prop)
     {
-        case 0: e.attr1 += amount; break;
+        case 0:
+        if (e.type == MAPMODEL)
+        {
+            e.attr1 += amount*16;
+            while (e.attr1 > 360) e.attr1 -= 360;
+            while (e.attr1 < 0) e.attr1 += 360;
+        }
+        else e.attr1 += amount;
+        break;
+
         case 1: e.attr2 += amount; break;
         case 2: e.attr3 += amount; break;
         case 3: e.attr4 += amount; break;
@@ -371,6 +409,26 @@
}

COMMANDF(scalelights, "ii", (int *f, int *i) { scalelights(*f, *i); });
+
+void scalelight(int f, int intens)
+{
+    entity &e = ents[closestent()];
+    if(e.type!=LIGHT) return;
+
+    e.attr1 = e.attr1*f/100;
+    if(e.attr1<2) e.attr1 = 2;
+    if(e.attr1>32) e.attr1 = 32;
+
+    if(intens)
+    {
+        scalecomp(e.attr2, intens);
+        scalecomp(e.attr3, intens);
+        scalecomp(e.attr4, intens);
+    }
+    calclight();
+}
+
+COMMANDF(scalelight, "ii", (int *f, int *i) { scalelight(*f, *i); });

int findentity(int type, int index)
{
--- worldlight.cpp    Tue Oct 22 13:57:19 2013
+++ worldlight.cpp    Fri Apr 11 15:30:00 2014
@@ -369,6 +369,49 @@
{
     blockpaste(b, b.x, b.y, false);
}
+
+void halfblockpaste(const block &b, int bx, int by, bool floor)
+{
+    const sqr *q = (const sqr *)((&b)+1);
+    for(int y = by; y<b.ys+by; y++)
+    for(int x = bx; x<b.xs+bx; x++)
+    {
+        if (q->type == SOLID || q->type == CORNER ||
+        //don't override heightfields with space unnessesarily
+        (q->type == SPACE && ((floor && S(x,y)->type != CHF) || (!floor && S(x,y)->type != FHF))))
+
+        S(x,y)->type = q->type;
+
+        if (floor || q->type == SOLID)
+        {
+            if (q->type == FHF ||
+            S(x-1,y)->type == FHF ||
+            S(x,y-1)->type == FHF ||
+            S(x-1,y-1)->type == FHF)
+            //
+            {S(x,y)->type = q->type; S(x,y)->vdelta = q->vdelta;}
+
+            S(x,y)->floor = q->floor;
+            S(x,y)->wtex = q->wtex;
+            S(x,y)->ftex = q->ftex;
+        }
+        else
+        {
+            if (q->type == CHF ||
+            S(x-1,y)->type == CHF ||
+            S(x,y-1)->type == CHF ||
+            S(x-1,y-1)->type == CHF)
+            //
+            {S(x,y)->type = q->type; S(x,y)->vdelta = q->vdelta;}
+
+            S(x,y)->ceil = q->ceil;
+            S(x,y)->utex = q->utex;
+            S(x,y)->ctex = q->ctex;
+        }
+
+        q++;
+    }
+}

void freeblock(block *&b)
{

Linux and Mac ports needed.
Why don't you just fork it on github?
Thanks given by:
#15
Because it will likely never be assimilated so it feels like a waste of time.
I'd do a lot more with the engine itself if any of it meant anything.

For the time being these helped me create maps usable with the public map format with more intuitive functionality and it took time to develop so I'm releasing it as is to whoever it helps.
Thanks given by:
#16
You don't have to submit it to the main AC project. Just fork it as 1.2 Mapping Client so people can clone it directly from that URL instead of looking through your differences.
Thanks given by:
#17
If you could put it on github, or you have another way to pass me the source code, it should be pretty easy to create a binary and a bash script for running it on *nix...
Thanks given by:
#18
Use a diff tool to apply the patch to the linked source. Phantom was going to build it originally but I think he died.
Thanks given by:
#19
(22 Aug 14, 07:49AM)Felix-The-Ghost Wrote: Phantom was going to build it originally but I think he died.

No but my Linux PC did :(
Thanks given by:
#20
Well it took over six years but I added screen captures to the first post, edited for readability, and included the diff if someone wants to try porting it :D
Thanks given by: 1Cap , Toriality