[Download] 1.2 Mapping Client
#1
What is this?
Being a mapper very familiar with the Cube engine I designed some mapping features I always wanted in AssaultCube, as well as features that I believe improve workflow dramatically. This download does not replace your old AssaultCube client, so you can have both.

Features
General Changes
The entity info (lower left) formatting has changed in a way I think you'll find convenient:
  1. The entity name is now colorized to match the color of the entity "sparkle" of its corresponding type.

  2. The formatting is broken into three lines: the first shows the status of the entity (entity selection locked or not), the second is the standard entity info, and the third line is used for sounds and mapmodels by displaying the file name/location of the resource on disk.
Entity sparkle colors have been adjusted:
  1. All CLA related entities are red
  2. All RVSF related entities are blue
  3. FFA spawns are green
  4. Ammobox, pistol clip, grenades, and akimbo are orange
  5. Health, helmet, kevlar are yellow-green
  6. Lights are white
  7. Sounds are cyan
  8. Ladders are bright cyan
  9. Mapmodels are purple
  10. Player Clips are magenta (like the bounding box)
  11. Regular Clips are Yellow (like the bounding box)
Again, the entity info (lower left display) matches those colors so identifying an entity type from a distance is easier.

The arrow drawn from nearby/selected player spawns is now colorized for CLA/RVSF/FFA

The closest entity's sparkle color no longer changes to blue -- it now radiates smoke when it is the closest/active entity while retaining its sparkle color.

"Solid" cubes now have purple grid guides to differentiate them from non-solid cubes (previously ignored)

The editing grid and selection edges now fade out when the mouse is idle for a few moments (a great compliment to cleanedit mode to automate the transitions between making manual selections and editing cubes)

Editing the first attribute of mapmodels (the rotation) using domodifier (holding the [1] key and scrolling the mouse wheel) is more intuitive (needs adjustments)

New Commands
Refer to the in-game documentation for use.
  1. cleanedit - a mode that hides all editing guides that you can't see in normal mode for unobstructed editing (does not hide grid or selection edges as they fade out automatically)
  2. togglecleanedit - toggles cleanedit mode (recommended bind)
  3. paste_half - pastes only floor cubes or only ceiling cubes from copied selection
  4. togglelockselent - toggles entity selection lock (recommended bind)
  5. filter_sel - very powerful command for adjusting selections based on parameters (type /filter_sel in game for the list of filters)
  6. savesel - stores selection info in memory
  7. reselect - restores selection(s) from memory
  8. scalelight - scales an individual light
  9. curtex - prints all texture slot information of cubes
  10. getcurtex - returns specific texture info for a cube, suitable for passing arguments to other functions
  11. curheight - prints all height info of cubes
  12. getcurheight - returns specific height info for a cube, suitable for passing arguments to other functions
  13. texname - returns the filename of a cube's texture
  14. undo_sel - removes most recent selection when you make complex selections and goof up
  15. select_all - selects entire map
  16. clearrecenttex - clears the history of recent textures used in the current map
  17. tex2front - brings textures to the front of the history list for immediate application
Download
Windows - 1.2MB
This doesn't overwrite your current client so you can use them side-by-side if you wish. 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.

Documentation
You're gonna want these if you plan on using any new commands (brings up in-game documentation and needed for TAB auto-completion of these commands)

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

Code:
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)

Disclaimer
Warning: The following statements make me sound ub3r n00b

I can't compile AssaultCube "out of the box" -- even without changes to the code I need to modify the makefile to get it to compile, which makes libcurl, a dependency introduced in 1.2 dynamically linked (requires a .dll -- included)

I can't seem to link my C/C++ libraries statically using the normal methods again from the makefile. This requires two more (included) .dll files. I apologize for this if you don't like .dll dependencies but it isn't too much more to download.

What I Would Like from You
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.
Thanks given by:
#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

Code:
--- 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

Code:
--- 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: