[redeclipse] 01/05: New upstream version 1.5.6

Markus Koschany apo at moszumanska.debian.org
Sat Sep 24 19:47:19 UTC 2016


This is an automated email from the git hooks/post-receive script.

apo pushed a commit to branch master
in repository redeclipse.

commit 19b0890f84626ef4d9327d821b0897cba09ba018
Author: Markus Koschany <apo at debian.org>
Date:   Sat Sep 24 20:42:53 2016 +0200

    New upstream version 1.5.6
---
 .github/ISSUE_TEMPLATE.md      |  16 +-
 bin/version.txt                |   2 +-
 config/fonts/emphasis.cfg      |   2 +-
 config/keymap.cfg              |   9 +
 config/menus/editing.cfg       | 304 +++++++----------------
 config/menus/game.cfg          |   2 +-
 config/menus/glue.cfg          | 240 +++++++++---------
 config/menus/help.cfg          |  95 ++++---
 config/menus/main.cfg          |   8 +-
 config/menus/maps.cfg          | 105 ++++----
 config/menus/options.cfg       | 551 +++++++++++++++++++++++------------------
 config/menus/profile.cfg       | 107 +++++---
 config/menus/servers.cfg       |  38 +--
 config/menus/vars.cfg          |  39 ++-
 config/setup.cfg               |  24 +-
 config/usage.cfg               |  72 ++++--
 sql/stats/create.sql           |   3 +-
 src/engine/client.cpp          |  12 +-
 src/engine/command.cpp         |   1 +
 src/engine/decal.cpp           |   2 +
 src/engine/irc.cpp             |  12 +-
 src/engine/main.cpp            |   8 +-
 src/engine/master.cpp          |   2 +-
 src/engine/movie.cpp           |   2 +-
 src/engine/rendergl.cpp        |   2 +-
 src/engine/renderparticles.cpp |   6 +-
 src/engine/rendersky.cpp       |   3 +-
 src/engine/rendertext.cpp      |  12 +-
 src/engine/server.cpp          |  14 +-
 src/engine/sound.cpp           |   4 +-
 src/engine/ui.cpp              |   4 +-
 src/engine/version.h           |   4 +-
 src/engine/worldio.cpp         |   7 +-
 src/game/ai.cpp                |   8 +-
 src/game/ai.h                  |   1 +
 src/game/auth.h                |   1 +
 src/game/client.cpp            |  48 ++--
 src/game/entities.cpp          |   2 +-
 src/game/game.cpp              |  21 +-
 src/game/game.h                |  24 +-
 src/game/hud.cpp               |  40 ++-
 src/game/physics.cpp           |  32 +--
 src/game/projs.cpp             |  12 +-
 src/game/scoreboard.cpp        |  10 +-
 src/game/server.cpp            | 125 ++++++----
 src/game/vars.h                |   2 +-
 src/game/weapons.cpp           |  34 ++-
 src/game/weapons.h             |   8 +-
 src/install/win/redeclipse.nsi |   4 +-
 src/semdeploy.sh               |  43 ++--
 src/shared/geom.h              |   5 +-
 src/shared/iengine.h           |   7 +-
 src/shared/igame.h             |   1 +
 version.txt                    |   2 +-
 54 files changed, 1147 insertions(+), 995 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 34a8b7b..80dc2ab 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,9 +1,17 @@
-## Have you verified that you can reproduce the problem? [Y/N]
+**Note**: Only report a feature request if it was already discussed on the forums. For reporting bugs and other problems, you can delete this note and use the following template. You can insert screenshots with drag&drop.
 
-## Have you checked on the forums or issue tracker to see if your issue has already been reported and/or resolved upstream? [Y/N]
 
-## Provide a good description along with a link to your original forum topic (if applicable).
+---
+**First seen in**: commit ... / stable version ... / current master
 
+**Related forum topic**: http://redeclipse.net/forum/viewtopic.php?f=7&t=...
 
-## Attach, or provide direct links to, any files which demonstrates your issue and any patches you might have here.
+**Observation**: 
 
+**How to reproduce**:
+1. 
+2. 
+... 
+
+I searched the issue list and checked that this is not a duplicate :white_check_mark: 
+I tried to rename my *config.cfg*, and the problem also occurs with default settings :white_check_mark: 
diff --git a/bin/version.txt b/bin/version.txt
index f57d439..ae52215 100644
--- a/bin/version.txt
+++ b/bin/version.txt
@@ -1 +1 @@
-0bf21279c8f36a2fabd319cb91f4251738dd51aa
+439365b7104e23880da027cabd211113e4234f44
diff --git a/config/fonts/emphasis.cfg b/config/fonts/emphasis.cfg
index 4f9057d..d7912f0 100644
--- a/config/fonts/emphasis.cfg
+++ b/config/fonts/emphasis.cfg
@@ -1,3 +1,3 @@
 fontalias "emphasis" "default"
-fontscale 68
+fontscale 60
 
diff --git a/config/keymap.cfg b/config/keymap.cfg
index 83e183b..05ff941 100644
--- a/config/keymap.cfg
+++ b/config/keymap.cfg
@@ -7,6 +7,15 @@ keymap -4 MOUSE4
 keymap -5 MOUSE5 
 keymap -6 MOUSE6
 keymap -7 MOUSE7
+keymap -8 MOUSE8
+keymap -9 MOUSE9
+keymap -10 MOUSE10
+keymap -11 MOUSE11
+keymap -12 MOUSE12
+keymap -13 MOUSE13
+keymap -14 MOUSE14
+keymap -15 MOUSE15
+keymap -16 MOUSE16
 keymap 8 BACKSPACE 
 keymap 9 TAB 
 keymap 13 RETURN 
diff --git a/config/menus/editing.cfg b/config/menus/editing.cfg
index e39763b..a2d586d 100644
--- a/config/menus/editing.cfg
+++ b/config/menus/editing.cfg
@@ -1,32 +1,4 @@
 
-    // button for editing commands with built-in
-    // key display, tooltip and r-click for console
-    guieditbutton = [ 
-        local cmd // in case there is semicolon to escape
-        cmd = (stringreplace $arg2 ";" "^";^"")
-        guibody [     
-            guibutton $arg1
-            guistrut 0.5
-            guispring 1
-            guibutton (dobindsearch [@arg2] edit) 
-        ] [@arg2] [saycommand /@cmd] [guitooltip [/@@arg2]]
-    ]
-
-    // same for looking up an editvarbind on a checkbox
-    // looks a bit complicated, but works fine
-    guieditcheckbox = [
-        guibody [     
-            guicheckbox $arg1 $arg2
-            guistrut 0.5
-            guispring 1
-            guibutton (dobindsearch [@arg2 (= $@arg2 0); if (= $@arg2 0) [echo @@arg2 OFF] [ echo @@arg2 ON]] edit) 
-        ] [if (= $@arg2 0) [echo @@arg2 OFF] [ echo @@arg2 ON]] [saycommand /@arg2] [guitooltip [/@@arg2]]
-    ]
-
-    // for convenience
-    guieditbutton2 = [guilist [guieditbutton [@@arg1] [@@arg2]]]
-    guieditcheckbox2 = [guilist [guieditcheckbox [@@arg1] [@@arg2]]]
-
     // utilities
     entselbyindex = [entselect [(> (indexof $arg1 (entindex)) -1)]]
     setdesc "entselbyindex" "selects one or more entities using a list of indices, for example^n/entselbyindex $myentlist^n/entselbyindex [12 15 7]" "indexlist"
@@ -118,17 +90,15 @@
                     guispring 1
                     guieditbutton2 "save the map" [savemap]
                 ]
+                guitext (format "map file format version: %1" $mapversion)
+                guistrut 0.5
+                guitext (format "map size: 2^^%1 or %2 cubes = %3m" $mapsize (<< 1 $mapsize) (<< 1 (- $mapsize 3 )))
                 guilist [
-                    guitext (format "bounding box size: %1" (<< 1 $mapsize))
                     guispring 1
-                    guieditbutton2 "increase" [mapenlarge]
-                ]
-                guilist [
-                    guitext (format "map size (power): %1" $mapsize)
+                    guieditbutton2 "double" [mapenlarge]
                     guispring 1
-                    guieditbutton2 "cull empty space" [shrinkmap]
+                    guieditbutton2 "shrink to non-empty volume" [shrinkmap]
                 ]
-                guitext (format "map file format version: %1" $mapversion)
                 guistrut 0.5
                 if (isonline) [ guieditbutton "refresh map from server" getmap ]
                 guibutton "edit the config file" [notepad @mapname.cfg] []
@@ -250,16 +220,12 @@
             guispring 1
             imax = (listlen $maps)
             if (imax) [
-                slidermax = (max (- $imax 15) 0)
                 guilist [
-                    guistrut 30 1
-                    loop i (min 15 $imax) [
-                        j = (+ $i $slider)
-                        p = (at $maps $j)
-                        guibutton $p [selmap = @p] 
-                    ]
+                guiloopscrollbar j $imax 15 30 [
+                    p = (at $maps $j)
+                    guibutton $p [selmap = @p] 
+                ]
                 ]
-                if (slidermax) [guislider slider 0 $slidermax [] 1 1] [guistrut 3 ]
             ] [
                 guistrut 33 
             ]
@@ -326,7 +292,7 @@
                     guifield floatspeed 6 [ floatspeed $floatspeed ]
                 ]
                 guistrut 0.5
-                guitext (format "current editing grid size: %1" (<< 1 $gridpower))
+                guitext (format "grid size: %1 cubes =  %2m" (<< 1 $gridpower) (<< 1 (- $gridpower 3)))
                 guislider gridpower 0 (min (- $mapsize 1) 12) 
                 guistrut 0.5
                 guitext "undo cache size in MB"
@@ -335,6 +301,8 @@
         ]    
 
         guitab view
+        guieditcheckbox "show distance to selection" "crosshairdistance"
+        guistrut 0.5
         guieditcheckbox "toggle outline" "outline"
         guibutton "change outline colour" [pickcolour outlinecolour] [] $editingtex
         guistrut 0.5
@@ -427,7 +395,7 @@
         guieditbutton "toggle heightmap mode" [hmapedit (! $hmapedit); blendpaintmode 0] 
         guieditbutton "^fdcycle heightmap brushes via mouse wheel" [domodifier 9]
         guistrut 0.5
-        guitext (format "copy/paste is scaled by the current grid size: %1" (<< 1 $gridpower))
+        guitext (format "copy/paste is scaled by the grid size: %1 cubes =  %2m" (<< 1 $gridpower) (<< 1 (- $gridpower 3)))
         guislider gridpower 0 (min (- $mapsize 1) 12) 
         guieditbutton "^fdchange via mouse wheel" [domodifier 1]
 
@@ -450,12 +418,10 @@
                 guiradio " insert on the map" prefabcopy 0
             ]
             guistrut 1
-            slidermax = (max (- $imax 15) 0)
-            guicenter [
+            guilist [
                 guilist [
-                    guistrut 15 1
-                    loop i (min 15 $imax) [
-                        p = (at $prefabs (+ $i $slider))
+                    guiloopscrollbar j $imax 15 15 [
+                        p = (at $prefabs $j)
                         guibutton $p [
                             pasteprefab @p 
                             if $prefabcopy [
@@ -468,14 +434,13 @@
                         ] 
                     ]
                 ]
-                if (slidermax) [guislider slider 0 $slidermax [] 1 1]
                 guinocursorfx [guiprefabpreview $guirollovername -1 [] 7.5 1 []]
             ]
         ] [
             guitext "no obr files found in home/prefab"
         ]
         guistrut 1
-        guitext (format "prefabs are scaled by the current grid size: %1" (<< 1 $gridpower))
+        guitext (format "prefabs are scaled by the grid size: %1 cubes =  %2m" (<< 1 $gridpower) (<< 1 (- $gridpower 3)))
         guislider gridpower 0 (min (- $mapsize 1) 12) 
 
         guitab texturing
@@ -816,7 +781,7 @@
         guistrut 1
         guilist [
             guilist [
-                guilistsplit i 2 [
+                guilooplistsplit i 2 [
                     textures/grass
                     nobiax/grass01
                     nobiax/grass02
@@ -931,7 +896,7 @@
             ]
             guistrut 2
             guilist [
-                guilistsplit i 2 [1 2 3 4 5 6 7 8] [
+                guilooplistsplit i 2 [1 2 3 4 5 6 7 8] [
                     do [guifield point_ at i -20]
                 ]
             ]
@@ -965,35 +930,35 @@
 
     newgui cloudlayer [ guistayopen [
         guistatus "press ^fcEsc^fw when done to return to the previous menu"
-        if (= 0 $guipasses) [
-            slider = 0
-            imax = (listlen $cloudlist)
-        ] 
-        slidermax = (max (- $imax 5) 0)
-        guicenter [
-            loop i (min 5 $imax) [
-                j = (+ $i $slider)
-                p = (at $cloudlist $j)
-                guiimage $p [cloudlayer @p] 3 (=s $cloudlayer $p) "" [saycommand /cloudlayer @p] 
+        guilist [
+            guilist [
+                guiloopscrollbar j (listlen $cloudlist) 10 40 [
+                    p = (at $cloudlist $j)
+                    if (=s $p $cloudlayer) [
+                        guibutton $p [cloudlayer ""] [saycommand /cloudlayer ""] "" 0xffff00
+                    ] [
+                        guibutton $p [cloudlayer @p] [saycommand /cloudlayer @p] 
+                    ]
+                ]
             ]
-            if (slidermax) [guislider slider 0 $slidermax [] 1 1]
+            guinocursorfx [ guiimage (? $guirollovername $guirollovername $cloudlayer) [] 5 1] 
         ]
     ] ]
-
+    // same thing for envlayer
     newgui envlayer [ guistayopen [
         guistatus "press ^fcEsc^fw when done to return to the previous menu"
-        if (= 0 $guipasses) [
-            slider = 0
-            imax = (listlen $cloudlist)
-        ] 
-        slidermax = (max (- $imax 5) 0)
-        guicenter [
-            loop i (min 5 $imax) [
-                j = (+ $i $slider)
-                p = (at $cloudlist $j)
-                guiimage $p [envlayer @p] 3 (=s $envlayer $p) "" [saycommand /envlayer @p] 
+        guilist [
+            guilist [
+                guiloopscrollbar j (listlen $cloudlist) 10 40 [
+                    p = (at $cloudlist $j)
+                    if (=s $p $envlayer) [
+                        guibutton $p [envlayer ""] [saycommand /envlayer ""] "" 0xffff00
+                    ] [
+                        guibutton $p [envlayer @p] [saycommand /envlayer @p] 
+                    ]
+                ]
             ]
-            if (slidermax) [guislider slider 0 $slidermax [] 1 1]
+            guinocursorfx [ guiimage (? $guirollovername $guirollovername $envlayer) [] 5 1] 
         ]
     ] ]
 
@@ -1119,12 +1084,7 @@
             guistrut 0.5
             guilist [
                 guispring 1
-                guilist [
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
-                    guilist [
-                        guistrut 0.8
-                        guilist [
+                guibox2 -1 0xffffff [
                     guitext "rotation or flip:"
                     guiright [guieditbutton2 "90 degree"  [vrotate 1]]
                     guiright [guieditbutton2 "180 degree"  [vrotate 2]]
@@ -1132,18 +1092,9 @@
                     guiright [guieditbutton2 "left to right" [vrotate 4]]
                     guiright [guieditbutton2 "top to bottom" [vrotate 5]]
                     guiright [guieditbutton2 "reset"  [vrotate 0]]
-                        ]
-                        guistrut 0.8
-                    ]
-                    guistrut 0.2
                 ]
                 guispring 1
-                guilist [
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
-                    guilist [
-                        guistrut 0.8
-                        guilist [
+                guibox2 -1 0xffffff [
                     guitext "texture offset:"
                     guieditbutton "move up" [vdelta voffset 0 (* -1 @varpix)] [] $arrowtex
                     guieditbutton "move down" [vdelta voffset 0  @varpix] [] $arrowdowntex
@@ -1155,18 +1106,9 @@
                         guitext " ^fapixels"
                     ]
                     guieditbutton "reset offset" [voffset 0 0] [] 
-                        ]
-                        guistrut 0.8
-                    ]
-                    guistrut 0.2
                 ]
                 guispring 1
-                guilist [
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
-                    guilist [
-                        guistrut 0.8
-                        guilist [
+                guibox2 -1 0xffffff [
                     guitext "scale:"
                     guieditbutton "800.0%" [vscale 8]
                     guieditbutton "400.0%" [vscale 4]
@@ -1175,53 +1117,31 @@
                     guieditbutton " 50.0%" [vscale 0.5]
                     guieditbutton " 25.0%" [vscale 0.25]
                     guieditbutton " 12.5%" [vscale 0.125]
-                        ]
-                        guistrut 0.8
-                    ]
-                    guistrut 0.2
                 ]
                 guispring 1
-                guilist [
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
-                    guilist [
-                        guistrut 0.8
-                        guilist [
+                guibox2 -1 0xffffff [
                     guitext "scrolling:"
                     guieditbutton "+horizontal" [vdelta vscroll 0.1 0]
                     guieditbutton "-horizontal" [vdelta vscroll -0.1 0]
                     guieditbutton "+vertical" [vdelta vscroll 0 0.1]
                     guieditbutton "-vertical" [vdelta vscroll 0 -0.1]
                     guieditbutton "stop moving" [vscroll 0 0]
-                        ]
-                        guistrut 0.8
-                    ]
-                    guistrut 0.2
                 ]
                 guispring 1
             ]
             guistrut 1
             guicenter [ 
-                guilist [ 
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
+                guibox2 -1 0xffffff [
+                    guitext "customize the opacity when inside alpha material:" 
                     guilist [
-                        guistrut 0.8
-                        guilist [
-                            guitext "customize the opacity when inside alpha material:" 
-                            guilist [
-                                guifield vara 3 
-                                guitext " front side"
-                                guistrut 3
-                                guifield vara2 3 
-                                guitext " back side"
-                                guistrut 3
-                                guieditbutton "apply" [valpha @vara @vara2]
-                            ]
-                        ]
-                        guistrut 0.8
+                        guifield vara 3 
+                        guitext " front side"
+                        guistrut 3
+                        guifield vara2 3 
+                        guitext " back side"
+                        guistrut 3
+                        guieditbutton2 "apply" [valpha @vara @vara2]
                     ]
-                    guistrut 0.2
                 ]
             ]
 
@@ -1312,28 +1232,16 @@
                     guirgbsliders hex
                 ]
                 guispring 1
-                guilist [
-                    guibackground 0 0 0xffffff 1 1
-                    guistrut 0.2
-                    guilist [
-                        guistrut 0.8
-                        guilist [
-                            guieditbutton "specular (gloss)" [vdelta vshaderparam specscale @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
-                            guieditbutton "glow effect (shine)" [vdelta vshaderparam glowcolor @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
-                            guieditbutton "pulsing glow effect" [vdelta vshaderparam pulseglowcolor @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
-                            guieditbutton "envmap (reflection)" [vdelta vshaderparam envscale @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
-                        ]
-                        guistrut 0.8
-                    ]
-                    guistrut 0.2
+                guibox2 -1 0xffffff [
+                    guieditbutton "specular (gloss)" [vdelta vshaderparam specscale @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
+                    guieditbutton "glow effect (shine)" [vdelta vshaderparam glowcolor @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
+                    guieditbutton "pulsing glow effect" [vdelta vshaderparam pulseglowcolor @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
+                    guieditbutton "envmap (reflection)" [vdelta vshaderparam envscale @(divf $colr 255) @(divf $colg 255) @(divf $colb 255) ] 
                 ]
             ]
             guistrut 1
-            guicenter [ guilist [ guilist [
-                guibackground 0 0 0xffffff 1 1
-                guistrut 0.8
-                guilist [
-                    guistrut 0.6
+            guicenter [
+                guibox2 -1 0xffffff [
                     guilist [
                         guifield pt 4
                         guistrut 1
@@ -1343,16 +1251,11 @@
                         guistrut 3
                         guieditbutton2 "set pulseglow speed" [vdelta vshaderparam pulseglowspeed @pt]
                     ]
-                    guistrut 0.5
-                ]
-                guistrut 0.8
-            ] ] ]
+                ] 
+            ]
             guistrut 0.5
-            guicenter [ guilist [ guilist [
-                guibackground 0 0 0xffffff 1 1
-                guistrut 0.8
-                guilist [
-                    guistrut 0.2
+            guicenter [
+                guibox2 -1 0xffffff [
                     guitext "parallax deforms the texture for a pseudo-3d-effect based on the normal map:" 
                     guistrut 0.5
                     guilist [
@@ -1381,10 +1284,8 @@
                         guibutton "fish eye"   [par1 = 0.00; par2 = 2.0]
                         guispring 1
                     ]
-                    guistrut 0.2
                 ]
-                guistrut 0.8
-            ] ] ]
+            ]
 
             guitab blend
             if (= 0 $guipasses) [slotnum = 1]
@@ -1453,11 +1354,8 @@
             guicenter [guitext "and the components of the currently selected texture"]
             guicenter [guitext "for example, this allows to add pulseglow to a texture with glow"]
             guistrut 1
-            guicenter [ guilist [ guilist [
-                guibackground 0 0 0xffffff 1 1
-                guistrut 0.8
-                guilist [
-                    guistrut 0.5
+            guicenter [
+                guibox2 -1 0xffffff [
                     texs = (loopconcat i 5 [result (gettexname $getseltex $i)])
                     if (stringlen $texs) [
                         guilist [
@@ -1492,10 +1390,8 @@
                             ]
                         ]
                     ]
-                    guistrut 0.5
                 ]
-                guistrut 0.8
-            ] ] ]
+            ]
         ] [
             guifont "emphasis" [guicenter [guitext "no current selection"]]
             guistrut 0.5
@@ -1620,22 +1516,12 @@
                     append entgetlist (format "[(%1)^t %2]" (entindex) (entget))
                     imax = (+ $imax 1) 
                 ]
-                slidermax = (max (- $imax 15) 0)
-                guilist [
-                    guilist [
-                        guistrut 30 1
-                        loop i (min 14 $imax) [
-                            j = (+ $i $slider)
-                            guilist [
-                                guibutton (at $entgetlist $j) [
-                                    entcancel
-                                    entselect [(= (at $entindexlist @@j) (entindex))]
-                                    slider = 0
-                                ]
-                            ]
-                        ]
+                guiloopscrollbar j $imax 15 30 [
+                    guibutton (at $entgetlist $j) [
+                        entcancel
+                        entselect [(= (at $entindexlist @@j) (entindex))]
+                        slider = 0
                     ]
-                    if (slidermax) [guislider slider 0 $slidermax [] 1 1]
                 ]
                 guistrut 0.5
                 guicheckbox (format "^f%1 edit multiple ents: use with caution!" (? $groupedit zyw d)) groupedit 
@@ -2062,7 +1948,6 @@
 
     newgui sounds [ guistayopen [
         if (= 0 $guipasses) [
-            slider = 0
             mapsounds = ""
             imax = (getsound 1)
             loop i $imax [
@@ -2072,17 +1957,9 @@
         if (imax) [
             guitext "^fapick a sound entity to create"
             guistrut 1
-            slidermax = (max (- $imax 15) 0)
-            guicenter [
-                guilist [
-                    guistrut 40 1
-                    loop i (min 15 $imax) [
-                        j = (+ $i $slider)
-                        p = (at $mapsounds $j)
-                        guieditbutton $p [newent sound @j] 
-                    ]
-                ]
-                if (slidermax) [guislider slider 0 $slidermax [] 1 1]
+            guiloopscrollbar j $imax 15 40 [
+                p = (at $mapsounds $j)
+                guieditbutton $p [newent sound @j] 
             ]
         ] [
             guitext "no sounds found for this map configuration"
@@ -2091,7 +1968,6 @@
 
     newgui soundtest [ guistayopen [
         if (= 0 $guipasses) [
-            slider = 0
             sounds = ""
             imax = (getsound 0)
             loop i $imax [
@@ -2100,19 +1976,11 @@
         ] 
         if (imax) [
             guibutton "test the /sound command"
-            guibutton "show sound debugger" [showui cursounds]
+            guibutton "show sound debugger" [showgui cursounds]
             guistrut 1
-            slidermax = (max (- $imax 15) 0)
-            guicenter [
-                guilist [
-                    guistrut 30 1
-                    loop i (min 15 $imax) [
-                        j = (+ $i $slider)
-                        p = (format "%1^t%2" $j (at $sounds $j))
-                        guibutton $p [sound @j] [] $arrowrighttex
-                    ]
-                ]
-                if (slidermax) [guislider slider 0 $slidermax [] 1 1]
+            guiloopscrollbar j $imax 15 30 [
+                p = (format "%1^t%2" $j (at $sounds $j))
+                guibutton $p [sound @j] [] $arrowrighttex
             ]
         ] [
             guitext "could not load sound data"
@@ -2124,23 +1992,19 @@
         if (= 0 $guipasses) [
             slider = 0
             imax = (mapmodelindex)
-            slidermax = (max (- $imax 15) 0)
         ] 
         if (imax) [
             guitext "^fapick a mapmodel entity to create"
             guistrut 0.5
             guicheckbox "show a preview" mapmodelprevs
             guistrut 0.5
-            guicenter [
+            guilist [
                 guilist [
-                    guistrut 20 1
-                    loop i (min 15 $imax) [
-                        j = (+ $i $slider)
+                    guiloopscrollbar j $imax 15 25 [
                         p = (mapmodelindex $j)
                         guibutton $p [newent mapmodel @j] 
                     ]
                 ]
-                if (slidermax) [guislider slider 0 $slidermax [] 1 1]
                 if $mapmodelprevs [guinocursorfx [guimodelpreview $guirollovername "mapmodel" [] 7.5 1]]
             ]
         ] [
diff --git a/config/menus/game.cfg b/config/menus/game.cfg
index 865da39..b189328 100644
--- a/config/menus/game.cfg
+++ b/config/menus/game.cfg
@@ -207,7 +207,7 @@ client_tab = [
     ]
     if (& (mutators) $mutsbitffa) [
         // FFA layout, save space with three cols
-        guilistsplit cn 3 $cnlist [
+        guilooplistsplit cn 3 $cnlist [
             guistrut 25 1
             content = (getclientname $cn 1)
             guiradio (trimstring $content 1250) cnsel $cn
diff --git a/config/menus/glue.cfg b/config/menus/glue.cfg
index 1e300df..1291bf5 100644
--- a/config/menus/glue.cfg
+++ b/config/menus/glue.cfg
@@ -1,43 +1,127 @@
-vardef =  [guitext $arg2 ; [@[arg1]val] = $$arg1]
-varhdef = [guitext $arg2 ; [@[arg1]val] = (hexcolour $$arg1)]
-varfld =  [varname = [@[arg1]val] ; guifield $varname $arg2 [@arg1 $@varname]]
-varbit =  [varname = [@[arg1]val] ; guibitfield "" $arg2 [@arg1 $@varname]]
 
-vardef2 = [
-    guitext $arg2
-    [@[arg1]val1] = $[@[arg1]1]
-    [@[arg1]val2] = $[@[arg1]2]
+guitest = [newgui guitest [
+    guistayopen [@@arg1]
+    guistatus $guirolloveraction
+]; showgui guitest]
+
+guitip = [ guistatus (format "TIP: ^fa%1" (? (> $numargs 0) [@arg1] (showtip))) ]
+
+guispacing = [
+    guistrut (divf $arg1 $fontwidth) 1
+    guistrut (divf $arg2 $fontheight)
 ]
-varhdef2 = [
-    guitext $arg2
-    [@[arg1]val1] = (hexcolour $[@[arg1]1])
-    [@[arg1]val2] = (hexcolour $[@[arg1]2])
+guispacing2 = [
+    guistrut (divf $arg1 $fontwidth) 
+    guistrut (divf $arg2 $fontheight) 1
 ]
-varfld2 = [guilist [
-    varname1 = [@[arg1]val1]
-    guifield $varname1 $arg2 [[@@[arg1]1] $@varname1]
-    varname2 = [@[arg1]val2]
-    guifield $varname2 $arg2 [[@@[arg1]2] $@varname2]
-]]
-varbit2 = [guilist [
-    varname1 = [@[arg1]val1]
-    guibitfield "" $varname1 $arg2 [[@@[arg1]1] $@varname1]
-    varname2 = [@[arg1]val2]
-    guibitfield "" $varname2 $arg2 [[@@[arg1]2] $@varname2]
-]]
-varincr = [guilist [
-    varname1 = [@[arg1]val1]
-    guibutton "^fy-" [[@@[arg1]1] (- $@varname1 1)]
-    guifield $varname1 $arg2 [[@@[arg1]1] $@varname1]
-    guibutton "^fg+" [[@@[arg1]1] (+ $@varname1 1)]
-    varname2 = [@[arg1]val2]
-    guifield $varname2 $arg2 [[@@[arg1]2] $@varname2]
-]]
 
-guilistx = [
-    if (> $arg1 0) [ guilist [ guilistx (- $arg1 1) $arg2 ] ] [ @arg2 ]
+guilist2 = [ guilist [ guilist [ arg1 ] ] ]
+guiright = [ guilist [ guispring 1; arg1 ] ]
+guicenter = [ guilist [ guispring 1; arg1; guispring 1 ] ]
+guicenterz = [ guilist [ guispring 1; guilist [ arg1 ]; guispring 1 ] ]
+
+guibox = [ 
+    guilist [
+        guibackground $arg1 $guibgblend $arg2 1 1
+        guistrut 0.8
+        guilist [
+            guistrut 0.2
+            arg3
+            guistrut 0.2
+        ]
+        guistrut 0.8
+    ]
+]
+guibox2 = [guilist [guibox $arg1 $arg2 $arg3]]
+
+guimerge = [ guinoskinfx [guibody [guilist [guilist [guistrut $arg1]; guilist [arg2]]] $arg3 $arg4 $arg5] ]
+
+guilooplistsplit = [
+    guilist [
+        gli = 0
+        gll = (listlen $arg3)
+        glz = (div (+ $gll (- $arg2 1)) $arg2)
+        loop gla $arg2 [
+            guilist [
+                glt = (min (+ $gli $glz) $gll)
+                while [< $gli $glt] [
+                    $arg1 = (at $arg3 $gli)
+                    arg4
+                    gli = (+ $gli 1)
+                ]
+            ]
+            if (< (+ $gla 1) $arg2) [arg5]
+        ]
+    ]
 ]
 
+guiloopsplit = [
+    guilist [
+        gli = 0
+        glz = (div (+ $arg3 (- $arg2 1)) $arg2)
+        loop gla $arg2 [
+            guilist [
+                glt = (min (+ $gli $glz) $arg3)
+                while [< $gli $glt] [
+                    $arg1 = $gli
+                    arg4
+                    gli = (+ $gli 1)
+                ]
+            ]
+            if (< (+ $gla 1) $arg2) [arg5]
+        ]
+    ]
+]
+
+guiloopscrollbar = [
+    if (= 0 $guipasses) [slider = 0]
+    slidermax = (max (- $arg2 $arg3) 0)
+    guilist [
+        guilist [
+            guistrut $arg4 1
+            loop line (min $arg3 $arg2) [
+                $arg1 = (+ $line $slider)
+                arg5
+            ]
+        ]
+        if $slidermax [guislider slider 0 $slidermax [] 1 1]
+    ]
+]
+
+guicheckbox2 = [
+    guistayopen [ guibutton $arg1 [case $@arg2 0 [@@arg2 1] 1 (? @arg3 [@@arg2 2] [@@arg2 0]) 2 [@@arg2 0]] [] "checkbox" 0xFFFFFF 0xFFFFFF -1 1 (? $$arg2 "checkboxon" "") (? (= $$arg2 2) $guicheckboxtwocolour $guicheckboxcolour) ]
+]
+
+guiradio2 = [
+    guistayopen [ guibutton $arg1 [@arg2 @arg3] [] "radiobox" 0xFFFFFF 0xFFFFFF -1 1 (? (= $$arg2 $arg3) "radioboxon" "") $guiradioboxcolour ]
+]
+
+// button for editing commands with built-in key display, tooltip and r-click for console
+guieditbutton = [ 
+    local cmd // in case there is a semicolon to escape
+    cmd = (stringreplace $arg2 ";" "^";^"")
+    guibody [     
+        guibutton $arg1
+        guistrut 0.5
+        guispring 1
+        guibutton (dobindsearch [@arg2] edit) 
+    ] [@arg2] [saycommand /@cmd] [guitooltip [/@@arg2]]
+]
+guieditbutton2 = [guilist [guieditbutton [@@arg1] [@@arg2]]]
+
+// same for looking up an editvarbind on a checkbox
+guieditcheckbox = [
+    guibody [     
+        guicheckbox $arg1 $arg2
+        guistrut 0.5
+        guispring 1
+        guibutton (dobindsearch [@arg2 (= $@arg2 0); if (= $@arg2 0) [echo @@arg2 OFF] [ echo @@arg2 ON]] edit) 
+    ] [if (= $@arg2 0) [echo @@arg2 OFF] [ echo @@arg2 ON]] [saycommand /@arg2] [guitooltip [/@@arg2]]
+]
+guieditcheckbox2 = [guilist [guieditcheckbox [@@arg1] [@@arg2]]]
+
+guiswitch = [ guilist [ guilist [ if (arg1) [arg2] [arg3] ] ] ]
+
 guione = [
     guispring 1
     guilist [
@@ -51,7 +135,7 @@ guione = [
     guispring 1
 ]
 
-guibox = [
+guitwo = [
     guispring 1
     guilist [
         guilist [
@@ -75,8 +159,7 @@ guibox = [
     guispring 1
 ]
 
-guimerge = [ guinoskinfx [guibody [guilist [guilist [guistrut $arg1]; guilist [arg2]]] $arg3 $arg4 $arg5] ]
-guiarea = [
+guibigmacro = [ guistayopen [
     guilist [
         if (> $numargs 9) arg10
         guilist [
@@ -111,82 +194,8 @@ guiarea = [
         ]
         arg12
     ]
-]
-guipage = [ guistayopen [guiarea $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 $arg7 $arg8 $arg9 $arg10 $arg11 $arg12] ]
-guicontainer = [ guilist [ guilist [ if (arg1) [arg2] [arg3] ] ] ]
-guiright = [ guilist [ guispring 1; arg1 ] ]
-guicenter = [ guilist [ guispring 1; arg1; guispring 1 ] ]
-guicenterz = [ guilist [ guispring 1; guilist [ arg1 ]; guispring 1 ] ]
-guicenterx = [ guispring 1; arg1; guispring 1 ]
-
-guitip = [ guistatus (format "TIP: ^fa%1" (? (> $numargs 0) [@arg1] (showtip))) ]
-
-guilistsplit = [
-    guilist [
-        gli = 0
-        gll = (listlen $arg3)
-        glz = (div (+ $gll (- $arg2 1)) $arg2)
-        loop gla $arg2 [
-            guilist [
-                glt = (min (+ $gli $glz) $gll)
-                while [< $gli $glt] [
-                    $arg1 = (at $arg3 $gli)
-                    arg4
-                    gli = (+ $gli 1)
-                ]
-            ]
-            if (< (+ $gla 1) $arg2) [arg5]
-        ]
-    ]
-]
-
-guiloopsplit = [
-    guilist [
-        gli = 0
-        glz = (div (+ $arg3 (- $arg2 1)) $arg2)
-        loop gla $arg2 [
-            guilist [
-                glt = (min (+ $gli $glz) $arg3)
-                while [< $gli $glt] [
-                    $arg1 = $gli
-                    arg4
-                    gli = (+ $gli 1)
-                ]
-            ]
-            if (< (+ $gla 1) $arg2) [arg5]
-        ]
-    ]
-]
-
-guilistloop = [
-    i = 0
-    j = (+ (div $arg3 (* $arg1 $arg2)) 1)
-    loop a $j [
-        if (> $a 0) [ guitab $a ]
-        guilist [
-            loop b $arg1 [
-                guilist [
-                    loop c $arg2 [
-                        if (< $i $arg3) [
-                            arg4
-                            i = (+ $i 1)
-                            if (< $c (- $arg2 1)) [ guibar ]
-                        ]
-                    ]
-                ]
-                if (&& (< $i $arg3) (< $b (- $arg1 1))) [ guibar ]
-            ]
-        ]
-    ]
-]
-
-guicheckbox2 = [
-    guistayopen [ guibutton $arg1 [case $@arg2 0 [@@arg2 1] 1 (? @arg3 [@@arg2 2] [@@arg2 0]) 2 [@@arg2 0]] [] "checkbox" 0xFFFFFF 0xFFFFFF -1 1 (? $$arg2 "checkboxon" "") (? (= $$arg2 2) $guicheckboxtwocolour $guicheckboxcolour) ]
-]
+] ]
 
-guiradio2 = [
-    guistayopen [ guibutton $arg1 [@arg2 @arg3] [] "radiobox" 0xFFFFFF 0xFFFFFF -1 1 (? (= $$arg2 $arg3) "radioboxon" "") $guiradioboxcolour ]
-]
 
 savewarncmd = ""
 newgui savewarn [
@@ -335,3 +344,10 @@ newgui pickcolour [ guistayopen [
 ] ]
 
 pickcolour = [ scurvar = $arg1 ; hex = $$arg1; showgui pickcolour ]
+
+looplist i [
+    guitest guitip guispacing guispacing2 guilist2 guiright guicenter guicenterz
+    guibox guibox2 guimerge guilooplistsplit guiloopsplit guiloopscrollbar
+    guicheckbox2 guieditbutton guieditbutton2 guieditcheckbox guieditcheckbox2
+] [setcomplete $i 1]
+
diff --git a/config/menus/help.cfg b/config/menus/help.cfg
index 36d5a32..585d8d9 100644
--- a/config/menus/help.cfg
+++ b/config/menus/help.cfg
@@ -38,9 +38,7 @@ newgui help [
                     ]
                 ]
             ]
-            guistrut 0.5
-            guibar
-            guistrut 0.5
+            guistrut 1
         ]
         guione [
             guilist [
@@ -123,24 +121,29 @@ newgui modes-mutators [
         guilist [
             guilist [
                 guilist [
-                    guicenter [ guifont "emphasis" [ guitext "Modes" ] ]
-                    guibar
-                    loop i $modeidxnum [
-                        guicenter [ guibutton (at $modename $i) [showgui (at $modeidxname @i) 1] [] "" 0x00FFFF ]
+                    guifont "emphasis" [ guitext "Modes" ] 
+                    guistrut 0.5
+                    local modedemotex
+                    modedemotex = $spectatortex
+                    looplist i $modeidxname [
+                        guibutton $i [showgui @i 1] [] $[mode@[i]tex] 0x00FFFF
                     ]
                 ]
-                guistrut 0.5
-                guibar
-                guistrut 0.5
+                guistrut 8
                 guilist [
-                    guilist [
-                        guistrut 2
-                        guicenter [ guifont "emphasis" [ guitext "Mutators" ] ]
-                        guistrut 2
+                    guifont "emphasis" [ guitext "Mutators" ]
+                    guistrut 0.5
+                    local modeinstagibtex
+                    modeinstagibtex = $modeinstatex
+                    looplist i (sublist $mutsidxname 0 $modeidxnum) [
+                        guibutton $i [showgui @i 1] [] $[mode@[i]tex] 0xFFFF00
                     ]
-                    guibar
-                    looplist i $mutsidxname [
-                        guicenter [ guibutton $i [showgui @i 1] [] "" 0xFFFF00 ]
+                ]
+                guistrut 2
+                guilist [
+                    guistrut 1
+                    looplist i (sublist $mutsidxname $modeidxnum) [
+                        guibutton $i [showgui @i 1] [] $[mode@[i]tex] 0xFFFF00
                     ]
                 ]
             ]
@@ -267,16 +270,16 @@ newgui capture [
     guitab "tips & strategy"
     guifont "emphasis" [ guicenter [ guitext "TIPS & STRATEGY" ] ]
     guistrut 0.5
-    guitext (format "Secure your own team's flag by pressing %1 to make stealing it more difficult and defending it simpler." (dobindsearch affinity)) point -1 -1 2100
+    guitext "Being in close proximity of your team's flag will add to both your attack and defense, as well as buff your max health and health regeneration." point -1 -1 2100
     guistrut 0.25
-    guitext (format "You can drop a flag at any time by pressing %1. Drop your team's flag into lava or an open abyss to reset it back to the base." (dobindsearch affinity)) point -1 -1 2100
+    guitext (format "Press %1 to pick up the flag from your team's base, or to drop any flags you carry." (dobindsearch affinity)) point -1 -1 2100
+    guistrut 0.5
+    guitext "Often defenders carry their own flag to make it harder for the enemy to steal. Note that the carrier does not get any buffs from the flag." point -1 -1 2100
     guistrut 0.25
     guitext "Remember, defending your own flag is as important as stealing the flag from the enemy. Choose which role you will play and try to stick to it for at least half the match." point -1 -1 2100
     guistrut 0.25
     guitext "Beware of players who hold grenades in defending positions near a flag. Draw them away with your shots or bring a flamer to put out flames." point -1 -1 2100
     guistrut 0.25
-    guitext "Being in close proximity of your team's flag will add to both your attack and defense, as well as buff your max health and health regeneration." point -1 -1 2100
-    guistrut 0.25
     guitext "Use impulse boosts and parkour tricks to steal the flag and make your escape more efficiently." point -1 -1 2100
     guistrut 0.25
     guitext "In Multi Capture-the-Flag make your target the weakest team, remembering you can carry up to 4 flags!" point -1 -1 2100
@@ -836,11 +839,24 @@ newgui racemarathon [
 
 newgui accounts [
     guiheader "player accounts"
-    guifont "emphasis" [ guicenter [ guitext "PLAYER ACCOUNTS" ] ]
-    guistrut 0.5
+    guifont "emphasis" [ guicenter [ guitext "SUMMARY OF LEVELS" ] ]
+    guistrut 1
+    guilist [
+        guistrut 2
+        guilist [
+            looplist i [player supporter moderator operator administrator developer founder] [guitext $i $[priv@[i]tex]]
+        ]
+        guistrut 2
+        guilist [
+            guicenter [guitext "server specific:"]
+            looplist i [supporter moderator operator administrator] [guitext $i $[privlocal@[i]tex]]
+        ]
+        guistrut 2
+    ]
+    guistrut 1
     guifont "emphasis" [
-        guicenter [ guibutton "obtain an account" "showgui obtain-account" ]
-        guicenter [ guibutton "account auth levels" "showgui account-levels" ]
+        guicenter [ guibutton "how to obtain an account" "showgui obtain-account" ]
+        guicenter [ guibutton "auth levels in detail" "showgui account-levels" ]
     ]
 ]
 
@@ -872,17 +888,17 @@ newgui account-levels [
         ]
         guistrut 1
         guilist [
-            guitext "Registered players with regular auth accounts."
-            guitext "These are players who contributed to Red Eclipse in some way, be it financially or creatively."
-            guitext "These are regular moderators, capable of muting, kicking and putting players in quarantine."
-            guitext "These are advanced moderators, capable of banning and changing many server variables."
-            guitext "These are normal administrators, having full control of server settings, variables and connected players."
-            guitext "These are advanced administrators and core developers of the Red Eclipse project."
+            guitext "Registered players with regular auth accounts and no special permissions."
+            guitext "Trusted players who contributed to Red Eclipse have open access to most game variables."
+            guitext "Regular moderators are capable of muting, kicking and putting players in quarantine."
+            guitext "Advanced moderators with the permission to ban players from a server."
+            guitext "Normal administrators, having full control of server settings, and connected players."
+            guitext "Advanced administrators and core developers of the Red Eclipse project."
             guitext "These are the founders of the Red Eclipse project and creators of the game."
         ]
     ]
     guistrut 1
-    guitext "Furthermore, some ^fs^fclocal^fS privileges can be granted on individual servers:"
+    guitext "Furthermore, some ^fs^fclocal^fS privileges can be granted on individual servers, using ^fg/addpriv^fw or ^fg/addlocalop^fw:"
     guistrut 0.5
     guilist [
         guilist [
@@ -892,10 +908,10 @@ newgui account-levels [
         ]
         guistrut 1
         guilist [
-            guitext "These are players who contributed to the server in some way or received special recognition."
-            guitext "These are regular moderators, local to the server and capable of all the same as global moderators"
-            guitext "These are advanced moderators, local to the server and capable of all the same as global operators."
-            guitext "These are normal administrators, local to the server and capable of all the same as global administrators."
+            guitext "Players who are allowed to change game variables on the current server, e.g. clan members."
+            guitext "Server specific moderators, capable of all the same as global moderators."
+            guitext "Local server operators have the same permissions as global operators on that server."
+            guitext "Usually, these are the owners of a server, or their trusted friends."
         ]
     ]
     guistrut 1
@@ -913,7 +929,7 @@ newgui account-levels [
         ]
         guistrut 1
         guilist [
-            guitext "These are A.I. controlled players, bots that do not speak and exist to balance matches."
+            guitext "These are A.I. controlled players, bots that are added automatically to balance matches."
             guitext "These are default players with no account and no extra privileges."
         ]
     ]
@@ -968,7 +984,7 @@ newgui weapons [ guistayopen [
                     guitext "primary mode:"
                     guistrut 0.2
                     if $[@[weapsel]ammosub1] [
-                        t = (*f 0.001 (div $[@[weapsel]ammomax] $[@[weapsel]ammosub1]) $[@[weapsel]delayattack1])
+                        t = (*f 0.001 (div $[@[weapsel]ammomax] $[@[weapsel]ammosub1]) (+ $[@[weapsel]delayattack1] $[@[weapsel]cooktime1]))
                         guitext (format "uses %1 ammo per shot, %2 ammo in %3 seconds" $[@[weapsel]ammosub1]  $[@[weapsel]ammomax] $t) $[@[weapsel]cliptex] -1 $[@[weapsel]colour]
                     ] [
                         guitext "does not use any ammo" $[@[weapsel]cliptex] -1 $[@[weapsel]colour]
@@ -990,7 +1006,7 @@ newgui weapons [ guistayopen [
                     guitext "secondary mode:"
                     guistrut 0.2
                     if $[@[weapsel]ammosub1] [
-                        t = (*f 0.001 (div $[@[weapsel]ammomax] $[@[weapsel]ammosub2]) $[@[weapsel]delayattack2])
+                        t = (*f 0.001 (div $[@[weapsel]ammomax] $[@[weapsel]ammosub2]) (+ $[@[weapsel]delayattack2] $[@[weapsel]cooktime2]))
                         guitext (format "uses %1 ammo per shot, %2 ammo in %3 seconds" $[@[weapsel]ammosub2]  $[@[weapsel]ammomax] $t) $[@[weapsel]cliptex] -1 $[@[weapsel]colour]
                     ] [
                         guitext "does not use any ammo" $[@[weapsel]cliptex] -1 $[@[weapsel]colour]
@@ -1221,3 +1237,4 @@ newgui various-scores [
     guifont "emphasis" [ guicenter [ guitext "RACE" ] ]
     guitext "The scoring system in Race games is usually limited to the lap count of each player and team - or the fastest lap time when the Timed mutator is active. Even though deathmatch scoring awards can be earned in Gauntlet Race, they have no effect on a players score." "" -1 -1 2100
 ]
+
diff --git a/config/menus/main.cfg b/config/menus/main.cfg
index 60c3a0f..e632d60 100644
--- a/config/menus/main.cfg
+++ b/config/menus/main.cfg
@@ -5,15 +5,15 @@ setpersist showmainplayerprev 1
 setcomplete showmainplayerprev 1
 
 newgui main [
-    guibox [
+    guitwo [
         if $showmainplayerprev [ guicenter [
-            guicenter [ guiplayerpreview (getplayermodel) (getplayercolour -1) (getplayerteam 1) (weapselect) (getplayervanity) [showgui profile] 7.5 1 1]
+            guicenter [ guiplayerpreview (getplayermodel) (getplayercolour -1) (getplayerteam 1) (weapselect) (getplayervanity) [showgui profilediff; showgui profile] 7.5 1 1]
             guicenter [ guitext (getplayername) ]
         ] ]
     ] [
         guilist [ guistrut 30 ]
         guicenter [ guilist [ guifont "emphasis" [
-            guicenter [ guibutton "profile" "showgui profile" ]
+            guicenter [ guibutton "profile" "showgui profilediff; showgui profile" ]
             guicenter [ guibutton "user account" "showgui useraccount" ]
             guistrut 0.5
             if (&& (isconnected) (> (getvote) 0)) [
@@ -41,7 +41,7 @@ newgui main [
     ]
     cases $guirolloveraction "showservers" [
         guitooltip "display the server browser for online matches"
-    ] "showgui profile" [
+    ] "showgui profilediff; showgui profile" [
         guitooltip "edit your name, colour, loadout and model type"
     ] "showgui edit" [
         guitooltip "access commands to use while editing a map"
diff --git a/config/menus/maps.cfg b/config/menus/maps.cfg
index 7b65bda..cc98b90 100644
--- a/config/menus/maps.cfg
+++ b/config/menus/maps.cfg
@@ -353,8 +353,8 @@ mapsmenu = [
                     mutsvar (at $mutsname $n) modeselected mutsselected (<< 1 $n) mapcur
                 ] [ guistrut 3 ]
                 if (> (stringlen (gspmutname $modeselected 0)) 0) [
-                    guistrut 0.25
-                    guifont "emphasis" [ guitext "mode specific" ]
+                    guistrut 1
+                    guifont "emphasis" [ guitext "mode specific mutators" ]
                     guistrut 0.25
                     guiloopsplit n 3 $mutsidxgsn [
                         mut = (gspmutname $modeselected $n)
@@ -387,69 +387,64 @@ mapsmenu = [
                 ]
                 guistrut 0.25
                 guilist [
-                    guicontainer [1] [
-                        nummaps = (- (listlen $maplist) 1)
+                    nummaps = (- (listlen $maplist) 1)
+                    guilist [
                         guilist [
+                            guistrut $mapscount 1
+                            mapindex = (min (max 0 (- $nummaps $mapscount)) $mapindex) //safeguard
+                            mapnum = (min $mapnum $nummaps)
                             guilist [
-                                guistrut $mapscount 1
-                                mapindex = (min (max 0 (- $nummaps $mapscount)) $mapindex) //safeguard
-                                mapnum = (min $mapnum $nummaps)
-                                guilist [
-                                    guistrut 37 1
-                                    break = 0
-                                    loopwhile i $mapscount [= $break 0] [
-                                        q = (+ $mapindex $i)
-                                        curmap = (at $maplist $q)
-                                        cases $curmap "*" [
-                                            guitext "maps not in the rotation:"
-                                        ] "~" [
-                                            guitext "maps played recently:"
-                                        ] "." [
-                                            guitext "maps from sauerbraten:"
-                                        ] "-" [
-                                            guitext "^fanone for this search filter" "textures/info"
-                                        ] "?" [
-                                            break = 1
-                                        ] () [
-                                            guilist [
-                                                guiradio (stringreplace $curmap $mapextra (format "^fs^fy%1^fS" $mapextra)) mapnum $q
-                                                guispring 1
-                                                guistayopen [
-                                                    hasmap = (>= (indexof (? (= $modeselected $modeidxdemo) $demofavs $mapfavs) $curmap) 0)
-                                                    guibutton "" [
-                                                        if (= $modeselected $modeidxdemo) [
-                                                            demofavs = (? @@hasmap (listdel $demofavs @@curmap) (concat (listdel $demofavs @@curmap) @@curmap))
-                                                        ] [
-                                                            mapfavs = (? @@hasmap (listdel $mapfavs @@curmap) (concat (listdel $mapfavs @@curmap) @@curmap))
-                                                        ]
-                                                        resetmapgui = 1
-                                                    ] [] "checkbox" 0xFFFFFF 0xFFFFFF -1 1 (? $hasmap "checkboxon" "") $guicheckboxcolour
-                                                ]
+                                guistrut 37 1
+                                break = 0
+                                loopwhile i $mapscount [= $break 0] [
+                                    q = (+ $mapindex $i)
+                                    curmap = (at $maplist $q)
+                                    cases $curmap "*" [
+                                        guitext "maps not in the rotation:"
+                                    ] "~" [
+                                        guitext "maps played recently:"
+                                    ] "." [
+                                        guitext "maps from sauerbraten:"
+                                    ] "-" [
+                                        guitext "^fanone for this search filter" "textures/info"
+                                    ] "?" [
+                                        break = 1
+                                    ] () [
+                                        guilist [
+                                            guiradio (stringreplace $curmap $mapextra (format "^fs^fy%1^fS" $mapextra)) mapnum $q
+                                            guispring 1
+                                            guistayopen [
+                                                hasmap = (>= (indexof (? (= $modeselected $modeidxdemo) $demofavs $mapfavs) $curmap) 0)
+                                                guibutton "" [
+                                                    if (= $modeselected $modeidxdemo) [
+                                                        demofavs = (? @@hasmap (listdel $demofavs @@curmap) (concat (listdel $demofavs @@curmap) @@curmap))
+                                                    ] [
+                                                        mapfavs = (? @@hasmap (listdel $mapfavs @@curmap) (concat (listdel $mapfavs @@curmap) @@curmap))
+                                                    ]
+                                                    resetmapgui = 1
+                                                ] [] "checkbox" 0xFFFFFF 0xFFFFFF -1 1 (? $hasmap "checkboxon" "") $guicheckboxcolour
                                             ]
                                         ]
                                     ]
                                 ]
                             ]
-                            guilist [
-                                guitext "custom map or "
-                                guicheckbox "search filter" searchfilter 1 0 [resetmapgui = 1]
-                            ]
-                            guilist [
-                                mapextraval = $mapextra
-                                mapextranum = $nummaps
-                                guiradio "" mapnum $mapextranum
-                                guifield mapextraval 34 [mapextra = $mapextraval; mapnum = $mapextranum; resetmapgui = $searchfilter] -1 0 "" 0 "^fd<enter map name>" $searchfilter
-                            ]
                         ]
-                        guislider mapindex 0 (max (- $nummaps $mapscount) 0) [] 1 1
-                    ] [
-                        guistrut 40.25
-                        guistrut (+f $mapscount 2) 1
+                        guilist [
+                            guitext "custom map or "
+                            guicheckbox "search filter" searchfilter 1 0 [resetmapgui = 1]
+                        ]
+                        guilist [
+                            mapextraval = $mapextra
+                            mapextranum = $nummaps
+                            guiradio "" mapnum $mapextranum
+                            guifield mapextraval 34 [mapextra = $mapextraval; mapnum = $mapextranum; resetmapgui = $searchfilter] -1 0 "" 0 "^fd<enter map name>" $searchfilter
+                        ]
                     ]
+                    guislider mapindex 0 (max (- $nummaps $mapscount) 0) [] 1 1
                 ]
             ]
         ]
-        guistrut 0.25
+        guistrut 1
         guilist [
             if (! (isonline)) [
                 guitext "server type:"; guistrut 0.5
@@ -516,7 +511,7 @@ votenum = 0
 votemenuinit = [ voteindex = 0 ]
 
 votemenu = [
-    guipage vote 8 74 3.2 [1] (getvote) [
+    guibigmacro vote 8 74 3.2 [1] (getvote) [
         voteplayers = (getvote $i 0)
         votemode = (getvote $i 1)
         votemuts = (getvote $i 2)
@@ -588,7 +583,7 @@ favindex = 0
 favnum = 0
 
 favsmenu = [
-    guipage fav 8 74 3.2 [1] (listlen $gamefavs) [
+    guibigmacro fav 8 74 3.2 [1] (listlen $gamefavs) [
         favs = (at $gamefavs $i)
         favl = (stringreplace $favs "." " ")
         votemap = (at $favl 0)
diff --git a/config/menus/options.cfg b/config/menus/options.cfg
index f0a7ee0..3fc5e03 100644
--- a/config/menus/options.cfg
+++ b/config/menus/options.cfg
@@ -238,6 +238,7 @@ newgui options_graphics [
 newgui options_gameplay [
     guiheader "gameplay"
     guistrut 0.5
+    guistrut 50 1
     guilist [
         guitext "player perspective: "
         guiradio "first-person" thirdperson 0
@@ -245,47 +246,7 @@ newgui options_gameplay [
         guiradio "third-person" thirdperson 1
     ]
     guistrut 1
-    guitext "player colour tones in team games:"
-    playertone = 0
-    if (&& (= $playerovertone 0) (= $playerundertone 1)) [playertone = 1]
-    if (&& (= $playerovertone 0) (= $playerundertone 2)) [playertone = 2]
-    if (&& (= $playerovertone 0) (= $playerundertone 5)) [playertone = 3]
-    guilist [
-        guiradio "profile and team" playertone 1 [playerovertone 0; playerundertone 1]
-        guistrut 2
-        guiradio "team only" playertone 2 [playerovertone 0; playerundertone 2]
-        guistrut 2
-        guiradio "mixed colour" playertone 3 [playerovertone 0; playerundertone 5]
-    ]
-    guistrut 0.5
-    guicheckbox "show vanity items" vanitymodels
-    guistrut 0.5
-    guilist [
-        guitext "brighten models:"
-        guistrut 1
-        guispring 1
-        guilist [
-            guistrut 25 1
-            guislider fullbrightmodels 0 200 [] 0 0 0xFFFFFF 1
-        ]
-    ]
-    guistrut 0.5
-    hintslider = (*f 100 $playerhintblend)
-    guilist [
-        guitext "glow effect:"
-        guistrut 1
-        guispring 1
-        guilist [
-            guistrut 25 1
-            guislider hintslider 0 100 [playerhintblend (*f 0.01 $hintslider)] 0 0 0xFFFFFF 1
-        ]
-    ]
-    guistrut 1
-    if (= $thirdperson 0) [
-        guicheckbox "first-person bob effect" firstpersonbob 1 0
-        guicheckbox "first-person sway effect" firstpersonsway 1 0
-    ]
-    if (= $thirdperson 1) [
+    if $thirdperson [
         guilist [
             guitext "third-person view distance:"
             guistrut 1
@@ -297,7 +258,7 @@ newgui options_gameplay [
         ]
         guistrut 0.5
         guilist [
-            guitext "third-person side (inverted):"
+            guitext "third-person side:"
             guistrut 1
             guispring 1
             guilist [
@@ -312,50 +273,49 @@ newgui options_gameplay [
             guistrut 1
             guiradio "static" thirdpersoncursor 0
         ]
-    ]
-    guistrut 0.5
-    if (= $thirdperson 0) [
+        guistrut 0.5
         guilist [
-            guitext "first-person opacity:"
+            guitext "third-person opacity:"
             guispring 1
             guilist [
                 guistrut 25 1
-                firstpersonblendstorage = (*f $firstpersonblend 100)
-                guislider firstpersonblendstorage 0 100 [ firstpersonblend (divf $firstpersonblendstorage 100) ] 0 0 0xFFFFFF 1
+                thirdpersonblendstorage = (*f $thirdpersonblend 100)
+                guislider thirdpersonblendstorage 0 100 [ thirdpersonblend (divf $thirdpersonblendstorage 100) ] 0 0 0xFFFFFF 1
             ]
         ]
-    ]
-    if (= $thirdperson 1) [
+        guistrut 0.5
         guilist [
-            guitext "third-person opacity:"
+            guitext "third-person field of view:"
+            guistrut 1
             guispring 1
             guilist [
                 guistrut 25 1
-                thirdpersonblendstorage = (*f $thirdpersonblend 100)
-                guislider thirdpersonblendstorage 0 100 [ thirdpersonblend (divf $thirdpersonblendstorage 100) ] 0 0 0xFFFFFF 1
+                guislider thirdpersonfov 90 150
             ]
         ]
-    ]
-    guistrut 0.5
-    if (= $thirdperson 0) [
+    ] [ // first person options
+        guistrut 1.9
+        guicheckbox "first-person bob effect" firstpersonbob 1 0
+        guistrut 0.5
+        guicheckbox "first-person sway effect" firstpersonsway 1 0
+        guistrut 0.5
         guilist [
-            guitext "first-person field of view:"
-            guistrut 1
+            guitext "first-person opacity:"
             guispring 1
             guilist [
                 guistrut 25 1
-                guislider firstpersonfov 90 150
+                firstpersonblendstorage = (*f $firstpersonblend 100)
+                guislider firstpersonblendstorage 0 100 [ firstpersonblend (divf $firstpersonblendstorage 100) ] 0 0 0xFFFFFF 1
             ]
         ]
-    ]
-    if (= $thirdperson 1) [
+        guistrut 0.5
         guilist [
-            guitext "third-person field of view:"
+            guitext "first-person field of view:"
             guistrut 1
             guispring 1
             guilist [
                 guistrut 25 1
-                guislider thirdpersonfov 90 150
+                guislider firstpersonfov 90 150
             ]
         ]
     ]
@@ -380,17 +340,78 @@ newgui options_gameplay [
         ]
     ]
     guistrut 1
+    guitext "player colour tones in team games:"
+    playertone = 0
+    if (&& (= $playerovertone 0) (= $playerundertone 1)) [playertone = 1]
+    if (&& (= $playerovertone 0) (= $playerundertone 2)) [playertone = 2]
+    if (&& (= $playerovertone 0) (= $playerundertone 5)) [playertone = 3]
+    guilist [
+        guiradio "profile and team" playertone 1 [playerovertone 0; playerundertone 1]
+        guistrut 2
+        guiradio "team only" playertone 2 [playerovertone 0; playerundertone 2]
+        guistrut 2
+        guiradio "mixed colour" playertone 3 [playerovertone 0; playerundertone 5]
+    ]
+    guistrut 0.5
+    guicheckbox "show vanity items" vanitymodels
+    guistrut 0.5
+    guilist [
+        guitext "brighten models:"
+        guistrut 1
+        guispring 1
+        guilist [
+            guistrut 25 1
+            guislider fullbrightmodels 0 200 [] 0 0 0xFFFFFF 1
+        ]
+    ]
+    guistrut 0.5
+    hintslider = (*f 100 $playerhintblend)
+    guilist [
+        guitext "glow effect:"
+        guistrut 1
+        guispring 1
+        guilist [
+            guistrut 25 1
+            guislider hintslider 0 100 [playerhintblend (*f 0.01 $hintslider)] 0 0 0xFFFFFF 1
+        ]
+    ]
+    guistrut 0.5
+    guilist [
+        guitext "death animation:"
+        guistrut 1
+        guiradio "on" deathanim 2
+        guistrut 1
+        guiradio "not in duel/survivor" deathanim 3
+        guistrut 1
+        guiradio "never" deathanim 0
+    ]
+    guistrut 0.5
+    guilist [
+        guitext "blood and gore:"
+        guispring
+        guicheckbox "splatter" bloodscale
+        guispring
+        guicheckbox "gibs" gibscale
+        guispring
+        if $gibscale [
+            guicheckbox "sparks" bloodsparks"
+        ] [
+            guitext "sparks" "textures/checkbox" 0x808080 0x666666
+        ]
+        guispring
+    ]
+    guistrut 1
     guilist [
         guilist [
             guitext "skip weapons:"
             guistrut 2
         ]
-        guistrut 3
+        guistrut 5
         guilist [
             guicheckbox "^f($pistoltex)pistol" skippistol 10 0 [] $pistolcolour
             guicheckbox "^f($clawtex)claw" skipclaw 10 0 [] $clawcolour
         ]
-        guistrut 3
+        guistrut 5
         guilist [
             guicheckbox "^f($grenadetex)grenades" skipgrenade 10 0 [] $grenadecolour
             guicheckbox "^f($minetex)mines" skipmine 10 0 [] $minecolour
@@ -398,7 +419,7 @@ newgui options_gameplay [
         ]
     ]
     guistrut 0.5
-    guicheckbox "ignore pickups of skipped weapons" skippickup
+    guicenter [guicheckbox "ignore pickups of skipped weapons" skippickup]
     guistrut 1
     guilist [
         guitext "weapon zoom level:"
@@ -1157,114 +1178,135 @@ newgui options_menus [
     guiheader "menu options"
     guistrut 0.5
     guistayopen [
+        //disable hud borders options for now, not a menu option
+        //guilist [
+        //    guitext "set borders:"
+        //    guistrut 1
+        //    guifont "little" [ guitext "^fa(1 = top only, 2 = bottom only, 3 = top & bottom)" ]
+        //]
+        //guistrut 0.25
+        //guilist [
+        //    guitext "playing"
+        //    guispring 1
+        //    guistrut 1
+        //    guilist [
+        //        guistrut 8 1
+        //        guislider playborder 0 3
+        //    ]
+        //    guistrut 1
+        //    guitext "size:"
+        //    guistrut 0.5
+        //    guilist [
+        //        guistrut 22 1
+        //        _playbordersize = (*f $playbordersize 20)
+        //        guislider _playbordersize 0 20 [ playbordersize (divf $_playbordersize 20) ]
+        //    ]
+        //    guistrut 1
+        //    guibutton "choose colour" [ pickcolour playbordertone ] [] "" 0xFFFFFF
+        //]
+        //guilist [
+        //    guitext "editing"
+        //    guispring 1
+        //    guistrut 1
+        //    guilist [
+        //        guistrut 8 1
+        //        guislider editborder 0 3
+        //    ]
+        //    guistrut 1
+        //    guitext "size:"
+        //    guistrut 0.5
+        //    guilist [
+        //        guistrut 22 1
+        //        _editbordersize = (*f $editbordersize 20)
+        //        guislider _editbordersize 0 20 [ editbordersize (divf $_editbordersize 20) ]
+        //    ]
+        //    guistrut 1
+        //    guibutton "choose colour" [ pickcolour editbordertone ] [] "" 0xFFFFFF
+        //]
+        //guilist [
+        //    guitext "spectator"
+        //    guispring 1
+        //    guistrut 1
+        //    guilist [
+        //        guistrut 8 1
+        //        guislider specborder 0 3
+        //    ]
+        //    guistrut 1
+        //    guitext "size:"
+        //    guistrut 0.5
+        //    guilist [
+        //        guistrut 22 1
+        //        _specbordersize = (*f $specbordersize 20)
+        //        guislider _specbordersize 0 20 [ specbordersize (divf $_specbordersize 20) ]
+        //    ]
+        //    guistrut 1
+        //    guibutton "choose colour" [ pickcolour specbordertone ] [] "" 0xFFFFFF
+        //]
+        //guilist [
+        //    guitext "waiting"
+        //    guispring 1
+        //    guistrut 1
+        //    guilist [
+        //        guistrut 8 1
+        //        guislider waitborder 0 3
+        //    ]
+        //    guistrut 1
+        //    guitext "size:"
+        //    guistrut 0.5
+        //    guilist [
+        //        guistrut 22 1
+        //        _waitbordersize = (*f $waitbordersize 20)
+        //        guislider _waitbordersize 0 20 [ waitbordersize (divf $_waitbordersize 20) ]
+        //    ]
+        //    guistrut 1
+        //    guibutton "choose colour" [ pickcolour waitbordertone ] [] "" 0xFFFFFF
+        //]
+        //guilist [
+        //    guitext "background"
+        //    guispring 1
+        //    guistrut 1
+        //    guilist [
+        //        guistrut 8 1
+        //        guislider backgroundborder 0 3
+        //    ]
+        //    guistrut 1
+        //    guitext "size:"
+        //    guistrut 0.5
+        //    guilist [
+        //        guistrut 22 1
+        //        _backgroundbordersize = (*f $backgroundbordersize 20)
+        //        guislider _backgroundbordersize 0 20 [ backgroundbordersize (divf $_backgroundbordersize 20) ]
+        //    ]
+        //    guistrut 1
+        //    guibutton "choose colour" [ pickcolour backgroundbordertone ] [] "" 0xFFFFFF
+        //]
+        //guifont "little" [ guibutton "(reset all borders to default)" [ resetvar playborder ; resetvar playbordersize ; resetvar playbordertone ; resetvar editborder ; resetvar editbordersize ; resetvar editbordertone ; resetvar specborder ; resetvar specbordersize ; resetvar specbordertone ; resetvar waitborder ; resetvar waitbordersize ; resetvar waitbordertone ; resetvar backgroundborder ; resetvar backgroundbordersize ; resetvar backgroundbordertone ] ]
+
+        guitext "customize colours of gui and skin elements:"
+        guistrut 0.5
+        guiloopsplit i 3 (getvarinfo -1 1 0 256 128 "gui") [ // all hex colour vars matching gui
+            scurvar = (getvarinfo $i 1 0 256 128 "gui")
+            s = (stringreplace (stringreplace $scurvar "gui" "") "colour" "")
+            guibutton $s [pickcolour @scurvar] [] $modeeditingtex $$scurvar
+        ] [guistrut 5]
+        guistrut 1.5
         guilist [
-            guitext "set borders:"
-            guistrut 1
-            guifont "little" [ guitext "^fa(1 = top only, 2 = bottom only, 3 = top & bottom)" ]
-        ]
-        guistrut 0.25
-        guilist [
-            guitext "playing"
-            guispring 1
-            guistrut 1
-            guilist [
-                guistrut 8 1
-                guislider playborder 0 3
-            ]
-            guistrut 1
-            guitext "size:"
-            guistrut 0.5
-            guilist [
-                guistrut 22 1
-                _playbordersize = (*f $playbordersize 20)
-                guislider _playbordersize 0 20 [ playbordersize (divf $_playbordersize 20) ]
-            ]
-            guistrut 1
-            guibutton "choose colour" [ pickcolour playbordertone ] [] "" 0xFFFFFF
-        ]
-        guilist [
-            guitext "editing"
-            guispring 1
-            guistrut 1
-            guilist [
-                guistrut 8 1
-                guislider editborder 0 3
-            ]
-            guistrut 1
-            guitext "size:"
-            guistrut 0.5
-            guilist [
-                guistrut 22 1
-                _editbordersize = (*f $editbordersize 20)
-                guislider _editbordersize 0 20 [ editbordersize (divf $_editbordersize 20) ]
-            ]
-            guistrut 1
-            guibutton "choose colour" [ pickcolour editbordertone ] [] "" 0xFFFFFF
-        ]
-        guilist [
-            guitext "spectator"
-            guispring 1
-            guistrut 1
-            guilist [
-                guistrut 8 1
-                guislider specborder 0 3
-            ]
-            guistrut 1
-            guitext "size:"
-            guistrut 0.5
-            guilist [
-                guistrut 22 1
-                _specbordersize = (*f $specbordersize 20)
-                guislider _specbordersize 0 20 [ specbordersize (divf $_specbordersize 20) ]
-            ]
-            guistrut 1
-            guibutton "choose colour" [ pickcolour specbordertone ] [] "" 0xFFFFFF
-        ]
-        guilist [
-            guitext "waiting"
-            guispring 1
-            guistrut 1
-            guilist [
-                guistrut 8 1
-                guislider waitborder 0 3
-            ]
-            guistrut 1
-            guitext "size:"
-            guistrut 0.5
-            guilist [
-                guistrut 22 1
-                _waitbordersize = (*f $waitbordersize 20)
-                guislider _waitbordersize 0 20 [ waitbordersize (divf $_waitbordersize 20) ]
-            ]
-            guistrut 1
-            guibutton "choose colour" [ pickcolour waitbordertone ] [] "" 0xFFFFFF
-        ]
-        guilist [
-            guitext "background"
-            guispring 1
-            guistrut 1
             guilist [
-                guistrut 8 1
-                guislider backgroundborder 0 3
+                guicheckbox "show menu tooltips" guitooltips
+                guicheckbox "show game hints" guistatusline
+                guicheckbox "show client GPU info" showloadinggpu
+                guicheckbox "show versioning info" showloadingversion
+                guicheckbox "show official site link" showloadingurl
             ]
-            guistrut 1
-            guitext "size:"
-            guistrut 0.5
+            guistrut 5
             guilist [
-                guistrut 22 1
-                _backgroundbordersize = (*f $backgroundbordersize 20)
-                guislider _backgroundbordersize 0 20 [ backgroundbordersize (divf $_backgroundbordersize 20) ]
+                guitext "fill or skin style:"
+                guiradio "completely disabled" guiskinned 0
+                guiradio "basic rectangular fill" guiskinned 1
+                guiradio "skin without border" guiskinned 2
+                guiradio "default skin style" guiskinned 3
             ]
-            guistrut 1
-            guibutton "choose colour" [ pickcolour backgroundbordertone ] [] "" 0xFFFFFF
         ]
-        guifont "little" [ guibutton "(reset all borders to default)" [ resetvar playborder ; resetvar playbordersize ; resetvar playbordertone ; resetvar editborder ; resetvar editbordersize ; resetvar editbordertone ; resetvar specborder ; resetvar specbordersize ; resetvar specbordertone ; resetvar waitborder ; resetvar waitbordersize ; resetvar waitbordertone ; resetvar backgroundborder ; resetvar backgroundbordersize ; resetvar backgroundbordertone ] ]
-        guistrut 0.5
-        guicheckbox "show menu tooltips" guitooltips
-        guicheckbox "show game hints" guistatusline
-        guicheckbox "show client GPU info" showloadinggpu
-        guicheckbox "show versioning info" showloadingversion
-        guicheckbox "show official site link" showloadingurl
         guistrut 0.5
         guifont "little" [
             cases $guirollovername "show menu tooltips" [
@@ -1436,7 +1478,7 @@ bindactions = [
     "saycommand /" "saytextcommand (getsaycolour)" "sayteamcommand (getsaycolour)"
     "showcompass voice" "showcompass team" [if (&& (! (& (mutators) $mutsbitffa)) (!= (gamemode) 6)) [showcompass bot]]
     "showgui help" edittoggle "showgui maps 1" "showgui maps 2" "showservers" "showgui profile 2"
-    "showgui team" "setpriv 1"  thirdpersonswitch toggleconsole screenshot
+    "showgui team" "showgui clients"  thirdpersonswitch toggleconsole screenshotnohud screenshot
     "spectate 1" addbot delbot
 ]
 bindtitles = [
@@ -1447,7 +1489,7 @@ bindtitles = [
     "cmd input" "all chat" "team chat"
     "voice compass" "team compass" "bot compass"
     "help menu" "toggle editing" "maps menu" "show votes menu" "server menu" "loadout menu"
-    "team menu" "claim privileges" "toggle thirdperson" "toggle console" "screenshot"
+    "team menu" "clients menu" "toggle thirdperson" "toggle console" "screenshot no hud" "screenshot"
     "enter spectator" "add bot" "delete bot"
 ]
 
@@ -1491,7 +1533,7 @@ waitbindtitles = [
 
 editbindactions = [
     "showtexgui" edittoggle "showgui edit" "remip" "fullbright 0; patchlight"
-    "fullbright 0; calclight -1" "fullbright 0; calclight 1" savemap "changeoutline 1"
+    "fullbright 0; quicklight" "fullbright 0; calclight 1" savemap "changeoutline 1"
     [showcompass editing] [showgui materials] [if $blendpaintmode paintblendmap editdrag]
     [if $blendpaintmode rotateblendbrush editextend] selcorners  "universaldelta 1" "universaldelta -1"
     cancelsel entcancel editdel editcopy editpaste editcut editflip
@@ -1548,18 +1590,17 @@ newgui resetcontrols [
 newgui options_controls [
     guiheader  "keys"
     guicenter [
-        guilist [ guistrut 28 ]
+        // guilist [ guistrut 28 ]
         guilist [
             guistrut 0.5
-            guitext "click an action then press the desired keys you wish to bind (press ESC when finished):"
+            guitext "click a key field and press the desired keys to bind" 
+            guitext "    then press Esc to deselect the input field"
             guistrut 1
             guilist [
                 guitext "state:"
                 guistrut 1
-                loop i 4 [ guiradio (at $bindtypes $i) curbindtype $i; guistrut 1 ]
+                loop i 4 [ guiradio (at $bindtypes $i) curbindtype $i; guistrut 2 ]
                 //guicheckbox "show default bind if not found" curbindshow
-                guispring 1
-                guibutton "^frrestore default controls" [showgui resetcontrols]
             ]
             guistrut 1
             curbindsign = (at $bindcalls $curbindtype)
@@ -1567,17 +1608,22 @@ newgui options_controls [
             curbindacts = (concatword $curbindsign bindactions)
             curbindtits = (concatword $curbindsign bindtitles)
             curbindsrch = (concatword search $curbindsign binds)
-            guilistsplit n 2 $[@curbindacts] [
+            //guilooplistsplit n 2 $[@curbindacts] [
+            guiloopscrollbar i (listlen $[@curbindacts]) 10 50 [
+                n = (at $[@curbindacts] $i) 
                 guilist [
-                    guitext (tabify (concatword (at $[@curbindtits] $gli) " ") 5)
-                    [newbinds at curbindsign@gli] = ([@curbindsrch] $n 0 "" "" " " " " @curbindshow)
-                    guikeyfield [newbinds at curbindsign@gli] -24 [
+                    guitext (concatword (at $[@curbindtits] $i) " ") 
+                    [newbinds at curbindsign@i] = ([@curbindsrch] $n 0 "" "" " " " " @curbindshow)
+                    guispring
+                    guikeyfield [newbinds at curbindsign@i] -24 [
                         oldbinds = ([@@curbindsrch] @n 0 "" "" " " " "  @@curbindshow)
                         looplist j $oldbinds [echo @@curbindcall " " $j; @@curbindcall $j ""]
-                        looplist j $[newbinds@@curbindsign@@gli] [echo @@curbindcall $j " " @@n; @@curbindcall $j [@@@n]]
+                        looplist j $[newbinds@@curbindsign@@i] [echo @@curbindcall $j " " @@n; @@curbindcall $j [@@@n]]
                     ]
                 ]
-            ] [ guistrut 2 ]
+            ]// [ guistrut 2 ]
+            guistrut 0.5
+            guibutton "^frrestore all default key binds" [showgui resetcontrols]
         ]
     ]
 
@@ -1585,75 +1631,104 @@ newgui options_controls [
     guicenter [
         guilist [
             guistrut 3
+            guistrut 50 1
             guitext "mouse overall sensitivity"
+            guislider sensitivity 1 100
             guistrut 1
             guitext "mouse yaw sensitivity"
+            guislider yawsensitivity 1 100
             guistrut 1
             guitext "mouse pitch sensitivity"
+            guislider pitchsensitivity 1 100
             guistrut 1
             guicheckbox "invert y-axis" mouseinvert
-        ]
-        guistrut 3
-        guilist [
-            guistrut 3
-            guistrut 50 1
-            guislider sensitivity 1 100
-            guistrut 1
-            guislider yawsensitivity 1 100
-            guistrut 1
-            guislider pitchsensitivity 1 100
+            guistrut 1.5
+            guitext "Tip: You may want to adjust your system settings"
+            guitext "      for accelerated mouse movement."
         ]
     ]
 
     guitab "advanced"
     guivisibletab [
         guistrut 1
-        guicenter [guitext "click an icon to view and edit the corresponding key bind via the console (cancel with Esc)" "textures/menu"]
-        guistrut 1
-        // keys as listed in config/keyref.cfg with some different ordering
-        guilistsplit curkey 5 [
-            A B C D E F G H I J K L M N O P Q R S T U V W X Y Z SPACE BACKSPACE
-            0 1 2 3 4 5 6 7 8 9
-            F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15
-            TAB RETURN ESCAPE
-            MOUSE1 MOUSE2 MOUSE3 MOUSE4 MOUSE5
-            UP DOWN RIGHT LEFT INSERT DELETE HOME END PAGEUP PAGEDOWN
-            KP0 KP1 KP2 KP3 KP4 KP5 KP6 KP7 KP8 KP9
-            KP_PERIOD KP_DIVIDE KP_MULTIPLY KP_MINUS KP_PLUS KP_ENTER KP_EQUALS
-            CLEAR PAUSE EXCLAIM QUOTEDBL HASH DOLLAR AMPERSAND QUOTE
-            LEFTPAREN RIGHTPAREN ASTERISK PLUS COMMA MINUS PERIOD SLASH
-            COLON SEMICOLON LESS EQUALS GREATER QUESTION AT LEFTBRACKET BACKSLASH RIGHTBRACKET CARET UNDERSCORE BACKQUOTE
-            NUMLOCK CAPSLOCK SCROLLOCK
-            RSHIFT LSHIFT RCTRL LCTRL RALT LALT RMETA LMETA LSUPER RSUPER
-            MODE COMPOSE HELP PRINT SYSREQ BREAK MENU POWER EURO UNDO
-            ] [
-            guilist [
-                guistayopen [
-                    curbindacts = (getbind $curkey)
-                    guibody [guiimage "textures/player"    [saycommand /bind @curkey "["@curbindacts"]"] 0.5 0 [] "" (? (stringlen $curbindacts) 0x66ff66 0x666666)] [] [] (? (stringlen $curbindacts) [guitooltip (getbind $curkey)])
-                    guibody [guiimage "textures/editing"   [saycommand /editbind @curkey "["(geteditbind @curkey)"]"] 0.5 0 [] "" (? (!=s (geteditbind $curkey) $curbindacts) 0xffff00 0x666666)] [] [] (? (!=s (geteditbind $curkey) $curbindacts) [guitooltip (geteditbind $curkey)])
-                    guibody [guiimage "textures/spectator" [saycommand /specbind @curkey "["(getspecbind @curkey)"]"] 0.5 0 [] "" (? (!=s (getspecbind $curkey) $curbindacts) 0x00ffff 0x666666)] [] [] (? (!=s (getspecbind $curkey) $curbindacts) [guitooltip (getspecbind $curkey)])
-                    guibody [guiimage "textures/waiting"   [saycommand /waitbind @curkey "["(getwaitbind @curkey)"]"] 0.5 0 [] "" (? (!=s (getwaitbind $curkey) $curbindacts) 0xffaa00 0x666666)] [] [] (? (!=s (getwaitbind $curkey) $curbindacts) [guitooltip (getwaitbind $curkey)])
-                    guitext $curkey
-                    guistrut 3
-                ]
-            ]
-        ]
-        guistrut 1
-        guilist [
-            guispring 1
-            guitext "default bind" "textures/player" -1 0x66ff66
-            guispring 1
-            guitext "editing bind" "textures/editing" -1 0xffff00
-            guispring 1
-            guitext "spectator bind" "textures/spectator" -1 0x00ffff
-            guispring 1
-            guitext "wait bind" "textures/waiting" -1 0xffaa00
-            guispring 1
-            guitext "gray icons: not in use" "" 0x666666
-            guispring 1
-        ]
+    	guitext "Use below field to select some keys or mouse buttons"
+    	guikeyfield curkey -50 [] -1 0 "" "" 1
+    	guitext "Press Esc to deselect the field. Be careful when editing binds."
+    	// use the latest key pressed only
+    	if (> (listlen $curkey) 1) [curkey = (at $curkey (- (listlen $curkey) 1))]
+    	// guitext [binds for the latest key pressed: @curkey]
+    	guistrut 1
+    	guistrut 50 1
+    	curbind = (getbind $curkey)
+    	if $curkey [
+    	    if $curbind [
+    	        guibox 0x333333 0xffffff [
+    	            guibutton [regular bind for @curkey:] [saycommand /bind @curkey "["@curbind"]"]
+    	            guitext $curbind "" -1 -1 800
+    	        ]
+    	        // look up universal delta
+    	        if (=s "universaldelta" (at $curbind 0)) [
+    	            guistrut 0.5
+    	            guibox 0x333333 0xffffff [
+    	                guitext "this implies the following scroll actions:"
+    	                looplist i [game spec wait edit dead] [
+    	                    d = (format "delta_%1_0" $i)
+    	                    if (getalias $d) [
+    	                        guistrut 0.5
+    	                        guibutton [@d =] [saycommand /@d = "["$@d"]"]
+    	                        guitext $$d "" -1 -1 1200
+    	                    ]
+    	                ]
+    	                guitext "and more actions with modifier keys"
+    	            ]
+    	        ]
+    	        // look up modifiers for universal delta
+    	        if (=s "domodifier" (at $curbind 0)) [
+    	            guistrut 0.5
+    	            guibox 0x333333 0xffffff [
+    	                guitext "this implies the following scroll actions:"
+    	                looplist i [game spec wait edit dead] [
+    	                    d = (format "delta_%1_%2" $i (at $curbind 1))
+    	                    if (getalias $d) [
+    	                        guibutton [@d =] [saycommand /@d = "["$@d"]"]
+    	                        guitext $$d "" -1 -1 1200
+    	                    ]
+    	                ]
+    	            ]
+    	        ]
+    	    ] [
+    	        guibutton [no regular bind on @curkey. Click here to add one.] [saycommand /bind @curkey "["]
+    	    ] 
+    	    guistrut 1
+    	    // same thing for other bind types, mainly editbinds
+    	    looplist i [edit spec wait] [
+    	        [curbind at i] = ([get@[i]bind] $curkey) 
+    	        if (!=s $curbind $[curbind at i]) [
+    	            guibox 0x333333 0xffffff [
+    	                guibutton [@i bind for @curkey:] [saycommand /@[i]bind @curkey "["@[curbind at i]"]"]
+    	                guitext $[curbind at i] "" -1 -1 1200
+    	            ]
+    	            // look up modifiers for universal delta
+    	            if (=s "domodifier" (at $[curbind at i] 0)) [
+    	                guistrut 0.5
+    	                guibox 0x333333 0xffffff [
+    	                    guitext "this implies the following scroll actions:"
+    	                    looplist j [game spec wait edit dead] [
+    	                        d = (format "delta_%1_%2" $j (at $[curbind at i] 1))
+    	                        if (getalias $d) [
+    	                            guibutton [@d =] [saycommand /@d = "["$@d"]"]
+    	                            guitext $$d "" -1 -1 1200
+    	                        ]
+    	                    ]
+    	                ]
+    	            ]
+    	        ] [
+    	            guibutton [no @i bind on @curkey. Click here to add one.] [saycommand /@[i]bind @curkey "["]
+    	        ] 
+    	        guistrut 1
+    	    ] 
+    	] 
     ]
 ] [
-    if (= $guipasses 0) [ curbindtype = 0 ]
+    if (= $guipasses 0) [ curbindtype = 0; curkey = "" ]
 ]
diff --git a/config/menus/profile.cfg b/config/menus/profile.cfg
index 454b8df..36e34c5 100644
--- a/config/menus/profile.cfg
+++ b/config/menus/profile.cfg
@@ -2,7 +2,7 @@ profilebuttons = [
     guilist [ guifont "emphasis" [
         guistrut 2
         guibutton "^fgok" [
-            setinfo $playerprevname $playerprevcolour $playerprevmodel $playerprevvanity $playerprevlweap $playerprevrweap
+            setinfo $playerprevname $playerprevcolour $playerprevmodel $playerprevvanity $playerprevloadweap $playerprevrandweap
             cleargui 1
         ] []
         guistrut 3
@@ -14,10 +14,33 @@ profilebuttons = [
     ] ]
 ]
 
+newgui profilediff [
+    guiheader "profile"
+    guitext "Do you want to save"
+    guitext "the following changes?"
+    guistrut 1
+    looplist i [name colour model vanity loadweap randweap] [
+        if (!=s $[player at i] $[playerprev at i]) [
+            guicenter [
+                guitext @i
+            ]
+            skip = 0
+	]
+    ]
+    if $skip [cleargui 1]
+    guistrut 1
+    profilebuttons
+] [
+    skip = 1
+]
+
+
 profilepreview = [
     guiplayerpreview $playerprevmodel $playerprevcolour $playerprevteam $playerprevweap $playerprevvanity [playerprevinherit = 1; showgui playerprev] 7.6 1 1
 ]
 
+profiletab = [guitwo [profilepreview] [@arg1] [profilebuttons] ] // left, right, bottom right
+
 playerprevinherit = 0
 playerprevdisinherit = 0
 
@@ -26,20 +49,18 @@ newgui playerprev [
     guicenter [ guiplayerpreview $playerprevmodel $playerprevcolour $playerprevteam $playerprevweap $playerprevvanity [cleargui 1] 15 1 1 ]
     guistrut 0.25
     guicenter [
-        guilistx 2 [
-            guitext "team: "
-            teamneutraltex = $teamtex
-            loop t 5 [
-                push t (at "neutral alpha omega kappa sigma" $t) [
-                    guistayopen [ guifont "default" [ guibutton (format "^f[%1]^f(%2)" $[team@[t]colour] $[team@[t]tex]) [playerprevteam = @@@@t] ] ]
-                ]
+        guitext "team: "
+        teamneutraltex = $teamtex
+        loop t 5 [
+            push t (at "neutral alpha omega kappa sigma" $t) [
+                guistayopen [ guifont "default" [ guibutton (format "^f[%1]^f(%2)" $[team@[t]colour] $[team@[t]tex]) [playerprevteam = @@@@t] ] ]
             ]
-            guistrut 1
-            guitext "weapon: "
-            loop w $weapidxnum [
-                push w (at $weapname $w) [
-                    guistayopen [ guifont "emphasis" [ guibutton (format "^f[%1]^f(%2)" $[@[w]colour] [textures/weapons/@w]) [playerprevweap = @@@@w] ] ]
-                ]
+        ]
+        guistrut 1
+        guitext "weapon: "
+        loop w (- $weapidxnum 1) [ // exclude last id: melee
+            push w (at $weapname $w) [
+                guistayopen [ guifont "emphasis" [ guibutton (format "^f[%1]^f(%2)" $[@[w]colour] [textures/weapons/@w]) [playerprevweap = @@@@w] ] ]
             ]
         ]
     ]
@@ -59,32 +80,32 @@ newgui playerprev [
 loadweapall = 0
 loadweaps = [
     lwa = ""
-    lwl = (listlen $playerprevlweap)
-    lwo = (? (> $lwl $arg1) (at $playerprevlweap $arg1) -1)
+    lwl = (listlen $playerprevloadweap)
+    lwo = (? (> $lwl $arg1) (at $playerprevloadweap $arg1) -1)
     loop lw2 $weapidxloadout [
         if (= $lw2 $arg1) [
             lwa = (? $lw2 (concat $lwa $arg2) $arg2)
         ] [
-            lw3 = (? (> $lwl $lw2) (at $playerprevlweap $lw2) -1)
+            lw3 = (? (> $lwl $lw2) (at $playerprevloadweap $lw2) -1)
             if (&& $lw3 (= $arg2 $lw3)) [ lw3 = $lwo ]
             lwa = (? $lw2 (concat $lwa $lw3) $lw3)
         ]
     ]
-    if (listlen $lwa) [ playerprevlweap = $lwa ]
+    if (listlen $lwa) [ playerprevloadweap = $lwa ]
 ]
 
 randweaps = [
     rwa = ""
-    rwl = (listlen $playerprevrweap)
+    rwl = (listlen $playerprevrandweap)
     loop rw2 $weapidxloadout [
         rw3 = (> $rwl $rw2)
-        rwv = (? $rw3 (at $playerprevrweap $rw2) 1)
+        rwv = (? $rw3 (at $playerprevrandweap $rw2) 1)
         if (= $rw2 $arg1) [
             rwv = $arg2
         ]
         rwa = (? $rw2 (concat $rwa $rwv) $rwv)
     ]
-    if (listlen $rwa) [ playerprevrweap = $rwa ]
+    if (listlen $rwa) [ playerprevrandweap = $rwa ]
 ]
 
 // these names should match the types in config/vanities.cfg
@@ -114,7 +135,7 @@ newgui profile [
         guicenter [guitext (format "One last tip: You can perform wall-runs and kicks with the %1 key. Have fun!" (dobindsearch special))]
         guitab profile
     ] [guiheader profile] 
-    guibox [ profilepreview ] [
+    profiletab [
         guilist [
             guistrut 3
             guilist [
@@ -123,13 +144,22 @@ newgui profile [
                 guistrut 1
                 guicenter [ guifont "emphasis" [ guitext "player color:" ] ]
                 guicenter [
-                    guihexpreview $playerprevcolour "hex-value"
+                    if $playerprevcolour [guihexpreview $playerprevcolour "hex-value"] [
+                        w = (at $weapname $playerprevweap)
+                        guistayopen [ guiimage $[@[w]tex] [
+                            playerprevweap = (+ 1 $playerprevweap)
+                            if (= $playerprevweap (- $weapidxnum 1)) [playerprevweap = 0 ]
+                        ] 1.9 1 "" [] $[@[w]colour] ]
+                        guistrut 4.45
+                    ]
                     guistrut 1
                     guilist [
                         guirgbsliders playerprevcolour
                     ]
                 ]
-                guistrut 1
+                guistrut 0.2
+                guiright [guiradio "use the current weapon's colour  " playerprevcolour 0]
+                guistrut 0.5
                 guicenter [ guifont "emphasis" [ guitext "player model:" ] ]
                 guistrut 0.25
                 guicenter [
@@ -139,9 +169,9 @@ newgui profile [
                 ]
             ]
         ]
-    ] [ profilebuttons ]
+    ]
     guitab "loadout"
-    guibox [ profilepreview ] [
+    profiletab [
         guilist [
             guistrut 3
             guilist [
@@ -165,19 +195,19 @@ newgui profile [
                     guilist [
                         guicenter [ guitext "random weapon filter:" ]
                         guistayopen [
-                            gdr = (listlen $playerprevrweap)
+                            gdr = (listlen $playerprevrandweap)
                             guilist [
                                 guibackground 0x303030
                                 guispring 1
                                 loop w1 $weapidxloadout [
                                     w4 = (+ $w1 $weapidxoffset)
                                     w5 = (at $weapname $w4)
-                                    w3 = (? (> $gdr $w1) (at $playerprevrweap $w1) 1)
+                                    w3 = (? (> $gdr $w1) (at $playerprevrandweap $w1) 1)
                                     guilist [
                                         if $w3 [ guibackground 0xFFFFFF 0.01 $[@[w5]colour] 1 1 ]
                                         guistrut 0.125
                                         guiimage textures/weapons/@w5 [
-                                            rwv = (? (> (listlen $playerprevrweap) @w1) (at $playerprevrweap @w1) 1)
+                                            rwv = (? (> (listlen $playerprevrandweap) @w1) (at $playerprevrandweap @w1) 1)
                                             randweaps @w1 (! $rwv)
                                         ] 0.75 0 [textures/blank] [] (? $w3 $[@[w5]colour] 0x808080)
                                         guistrut 0.125
@@ -199,16 +229,15 @@ newgui profile [
                 guistrut 0.5
                 guicenter [ guicheckbox " pick a loadout every new game" showloadoutmenu ]
             ]
-            guibar
             guistrut 2
             guilist [
                 guibackground
                 guicenter [
                     guistayopen [
-                        gdl = (listlen $playerprevlweap)
+                        gdl = (listlen $playerprevloadweap)
                         gdw = 0
                         loop w2 (? $loadweapall $weapidxloadout $playermaxcarry) [
-                            w3 = (? (> $gdl $w2) (at $playerprevlweap $w2) -1)
+                            w3 = (? (> $gdl $w2) (at $playerprevloadweap $w2) -1)
                             hi = (mod $w2 2)
                             al = (|| (= $w3 0) (allowedweap $w3))
                             guilist [
@@ -261,9 +290,9 @@ newgui profile [
             ]
         ]
     guivisible [ guitip (format "press %1 to open this weapon menu at any time" (dobindsearch "showgui profile 2")) ]
-    ] [ profilebuttons ]
+    ] 
     guitab "vanity items"
-    guibox [ profilepreview ] [
+    profiletab [
         guilist [
             guistrut 3
             guilist [
@@ -333,7 +362,7 @@ newgui profile [
                 ]
             ]
         ]
-    ] [ profilebuttons ]
+    ]
 ] [
     if (= $guipasses 0) [
         if $playerprevdisinherit [ playerprevdisinherit = 0 ] [
@@ -344,17 +373,17 @@ newgui profile [
             playerprevmodel = (getplayermodel)
             playerprevvanity = (getplayervanity)
         ]
-        playerprevlweap = ""
+        playerprevloadweap = ""
         break = 0
         loopwhile i $weapidxloadout [= $break 0] [
             q = (getloadweap $i)
-            if (< $q 0) [ break = 1 ] [ playerprevlweap = (? $i (concat $playerprevlweap $q) $q) ]
+            if (< $q 0) [ break = 1 ] [ playerprevloadweap = (? $i (concat $playerprevloadweap $q) $q) ]
         ]
-        playerprevrweap = ""
+        playerprevrandweap = ""
         break = 0
         loopwhile i $weapidxloadout [= $break 0] [
             q = (getrandweap $i)
-            playerprevrweap = (? $i (concat $playerprevrweap $q) $q)
+            playerprevrandweap = (? $i (concat $playerprevrandweap $q) $q)
         ]
     ]
 ]
diff --git a/config/menus/servers.cfg b/config/menus/servers.cfg
index 4f292fb..181009a 100644
--- a/config/menus/servers.cfg
+++ b/config/menus/servers.cfg
@@ -59,7 +59,7 @@ servermenuiter = [
 servermenu = [
     sinfopause = 0
     sinfotimer = (- (getmillis 1) $sinfouitime)
-    guipage sinfo 5 96 4 [getserver] (getserver) [
+    guibigmacro sinfo 5 96 4 [getserver] (getserver) [
         sinfostat = (getserver $i 0 0)
         sinfoname = (getserver $i 0 1)
         sinfoport = (getserver $i 0 2)
@@ -105,6 +105,23 @@ servermenu = [
                 append searchbuffer (getserver $i 3 $j) // and handle
             ]
         ]
+        if (=s $sinfonpid $sinforetry) [
+            if (= $sinfowait 1) [
+                if (stringlen $sinfopass) [
+                    savewarnchk (format "connect %1 %2 %3" $sinfoname $sinfoport $sinfopass)
+                    sinfopass = ""
+                    sinforetry = ""
+                    sinfowait = 0
+                ]
+            ] [
+                if (|| [hasauthkey 1] [!= $sinfostat 3]) [
+                    savewarnchk (format "connect %1 %2" $sinfoname $sinfoport)
+                    sinfopass = ""
+                    sinforetry = ""
+                    sinfowait = 0
+                ]
+            ]
+        ]
     ] [(|| (= $searchfilter 0)  (> (stringcasestr $searchbuffer $searchstr) -1))] [
         if $updateservergui [
             guibutton "^fwThere are no servers to display, maybe ^fgupdate ^fwthe list?" updatefrommaster [] "info"
@@ -161,7 +178,7 @@ servermenu = [
                         sinfosecs = (? (>= $sinfolast 0) (div (- (getmillis 1) $sinfolast) 1000) -1)
                         guifont "little" [ guibutton (? (>= $sinfosecs 0) (format "^fa%1 ^fds ago" $sinfosecs) "^fdwaiting..") ]
                     ]
-                ] [ guicenter [ guifont "huge" [ guicenterx [ guibutton "?" ] ] ] ]
+                ] [ guicenter [ guifont "huge" [ guibutton "?" ] ] ]
             ]
             sinfoimage = "textures/emblem"
             if (&& [< $sinfoping $serverwaiting] [= $sinfogver (getversion 1)]) [
@@ -277,23 +294,6 @@ servermenu = [
                 ] [ guitooltip "^fano players online" ]
             ] [ guitooltip "^fano information available" ]
         ]
-        if (=s $sinfonpid $sinforetry) [
-            if (= $sinfowait 1) [
-                if (stringlen $sinfopass) [
-                    savewarnchk (format "connect %1 %2 %3" $sinfoname $sinfoport $sinfopass)
-                    sinfopass = ""
-                    sinforetry = ""
-                    sinfowait = 0
-                ]
-            ] [
-                if (|| [hasauthkey 1] [!= $sinfostat 3]) [
-                    savewarnchk (format "connect %1 %2" $sinfoname $sinfoport)
-                    sinfopass = ""
-                    sinforetry = ""
-                    sinfowait = 0
-                ]
-            ]
-        ]
     ] [
         guistrut 0.5
         guilist [
diff --git a/config/menus/vars.cfg b/config/menus/vars.cfg
index be03d43..243b0ae 100644
--- a/config/menus/vars.cfg
+++ b/config/menus/vars.cfg
@@ -70,34 +70,27 @@ newgui vars [
     ]
     guistrut 0.5
     guilist [
-        guilistx 2 [
-            guifont "little" [
-                guicontainer [1] [
+        guifont "little" [
+            guilist [
+                guilist [
+                    //guistrut $varscount 1
+                    varindex = (min (max 0 (- $numvars $varscount)) $varindex) //safeguard
+                    varnum = (min $varnum $numvars)
                     guilist [
-                        guilist [
-                            //guistrut $varscount 1
-                            varindex = (min (max 0 (- $numvars $varscount)) $varindex) //safeguard
-                            varnum = (min $varnum $numvars)
-                            guilist [
-                                guistrut 30 1 // expected max length of names
-                                break = 0
-                                loopwhile i $varscount [= $break 0] [
-                                    q = (+ $varindex $i)
-                                    if (< $q $numvars) [
-                                        curvar = (getvarinfo $q $vartypes $varnotypes $varflags $varnoflags $varsearchstr)
-                                        hilvar = (stringreplace $curvar $varsearchstr (format "^fs^fy%1^fS" $varsearchstr))
-                                        guilist [ guiradio $hilvar varnum $q ]
-                                    ] [ guistrut 1 ]
-                                ]
-                            ]
+                        guistrut 30 1 // expected max length of names
+                        break = 0
+                        loopwhile i $varscount [= $break 0] [
+                            q = (+ $varindex $i)
+                            if (< $q $numvars) [
+                                curvar = (getvarinfo $q $vartypes $varnotypes $varflags $varnoflags $varsearchstr)
+                                hilvar = (stringreplace $curvar $varsearchstr (format "^fs^fy%1^fS" $varsearchstr))
+                                guilist [ guiradio $hilvar varnum $q ]
+                            ] [ guistrut 1 ]
                         ]
                     ]
-                    guislider varindex 0 (max (- $numvars $varscount) 0) [] 1 1
-                ] [
-                    guistrut 43.25
-                    //guistrut (+f $varscount 1) 1
                 ]
             ]
+            guislider varindex 0 (max (- $numvars $varscount) 0) [] 1 1
         ]
         guistrut 1
         guilist [
diff --git a/config/setup.cfg b/config/setup.cfg
index 57eecbb..561f1a2 100644
--- a/config/setup.cfg
+++ b/config/setup.cfg
@@ -5,7 +5,7 @@ mapcomplete     = [ setcomplete $arg1 1; complete $arg1 maps mpz ]; mapcomplete
 start           = [ mode $arg2 $arg3; map $arg1 ]; mapcomplete start
 
 demo            = [ stopdemo; start $arg1 0 0 ]; complete demo demos dmo
-edit            = [ start $arg1 $modeidxediting (| $mutsbitffa $mutsbitclassic $arg2) ]; mapcomplete edit
+edit            = [ start $arg1 $modeidxediting (+ $mutsbitffa $mutsbitclassic $arg2) ]; mapcomplete edit
 deathmatch      = [ start $arg1 $modeidxdeathmatch (| 0 $arg2) ]; mapcomplete deathmatch; dm = [ deathmatch $arg1 $arg2 ]; mapcomplete dm
 teamdm          = [ start $arg1 $modeidxdeathmatch (| 0 $arg2) ]; mapcomplete teamdm; tdm = [ teamdm $arg1 $arg2 ]; mapcomplete tdm
 gladiator       = [ start $arg1 $modeidxdeathmatch (| $mutsbitgsp1 $arg2) ]; mapcomplete gladiator
@@ -19,7 +19,7 @@ race            = [ start $arg1 $modeidxrace (| 0 $arg2) ]; mapcomplete race
 instagib        = [ start $arg1 $modeidxdeathmatch (| $mutsbitinstagib $arg2) ]; mapcomplete instagib; insta = [ instagib $arg1 $arg2 ]; mapcomplete insta
 medieval        = [ start $arg1 $modeidxdeathmatch (| $mutsbitmedieval $arg2) ]; mapcomplete medieval
 kaboom          = [ start $arg1 $modeidxdeathmatch (| $mutsbitkaboom $arg2) ]; mapcomplete kaboom
-duel            = [ start $arg1 $modeidxdeathmatch (| $mutsbitduel $arg2) ]; mapcomplete duel
+duel            = [ start $arg1 $modeidxdeathmatch (+ $mutsbitduel $mutsbitffa $mutsbitbasic $mutsbithard $arg2) ]; mapcomplete duel
 survivor        = [ start $arg1 $modeidxdeathmatch (| $mutsbitsurvivor $arg2) ]; mapcomplete survivor; lms = [ survivor $arg1 $arg2 ]; mapcomplete lms
 classic         = [ start $arg1 $modeidxdeathmatch (| $mutsbitclassic $arg2) ]; mapcomplete classic
 quickcapture    = [ start $arg1 $modeidxcapture (| $mutsbitgsp1 $arg2) ]; mapcomplete quickcapture
@@ -30,11 +30,10 @@ kingdefend      = [ start $arg1 $modeidxdefend (| $mutsbitgsp2 $arg2) ]; mapcomp
 holdbomber      = [ start $arg1 $modeidxbomber (| $mutsbitgsp1 $arg2) ]; mapcomplete holdbomber
 basketbomber    = [ start $arg1 $modeidxbomber (| $mutsbitgsp2 $arg2) ]; mapcomplete basketbomber
 attackbomber    = [ start $arg1 $modeidxbomber (| $mutsbitgsp3 $arg2) ]; mapcomplete attackbomber
-timedrace       = [ start $arg1 $modeidxrace (| $mutsbitgsp1 $arg2) ]; mapcomplete timedrace; trial = [timedrace $arg1 $arg2 ]; mapcomplete trial
+trial           = [ start $arg1 $modeidxrace (+ $mutsbitgsp1 $mutsbitffa $arg2) ]; mapcomplete trial
 endurancerace   = [ start $arg1 $modeidxrace (| $mutsbitgsp2 $arg2) ]; mapcomplete endurancerace
 gauntletrace    = [ start $arg1 $modeidxrace (| $mutsbitgsp3 $arg2) ]; mapcomplete gauntletrace
 
-
 delta_game_0 = [ if (iszooming) [ setzoom $arg1 ] [ weapon -1 $arg1 ] ]
 delta_spec_0 = [ followdelta $arg1 ]
 delta_wait_0 = [ followdelta $arg1 ]
@@ -145,7 +144,8 @@ sayteamcommand =    [inputcommand $arg1 [sayteam $commandbuffer] (getplayerteami
 
 bind T              [ saytextcommand (getsaycolour) ]
 bind RETURN         [ saytextcommand (getsaycolour) ]
-bind BACKQUOTE      [ showgui console ]
+bind KP_ENTER       [ saytextcommand (getsaycolour) ]
+bind BACKQUOTE      "saycommand /"
 bind SLASH          "saycommand /"
 bind Y              [ sayteamcommand (getsaycolour) ]
 bind I              [ showgui lobby ]
@@ -173,9 +173,10 @@ bind F4             [ showgui maps 2 ]
 bind F5             [ showservers ]
 bind F6             [ showgui profile 2 ]
 bind F7             [ showgui team ]
-bind F8             [ setpriv 1 ]
+bind F8             [ showgui clients ]
 bind F9             [ thirdpersonswitch ]
-bind F11            [ toggleconsole ]
+bind F10            [ toggleconsole ]
+bind F11            [ screenshotnohud ]
 bind F12            [ screenshot ]
 bind COMMA          [ showgui profile 2 ]
 bind PERIOD         [ showgui team ]
@@ -184,8 +185,8 @@ editbind F1         [ showtexgui ]
 editbind F2         [ edittoggle ]
 editbind F3         [ showgui edit ]
 editbind F4         [ remip ]
-editbind F5         [ fullbright 0; patchlight]
-editbind F6         [ fullbright 0; calclight -1 ]
+editbind F5         [ fullbright 0; patchlight ]
+editbind F6         [ fullbright 0; quicklight ]
 editbind F7         [ fullbright 0; calclight 1 ]
 editbind F8         [ savemap ]
 editbind F9         [ changeoutline 1 ]
@@ -314,6 +315,11 @@ dobindsearch = [
     [search@[arg2]binds] $arg1 5 "^f{" "}" (? $textkeyseps (? $textkeyimages "|" ", ") (? $textkeyimages "" " ")) (? $textkeyseps (? $textkeyimages "|" " or ") (? $textkeyimages "" " "))
 ]
 
+screenshotnohud = [
+    sleep 50 [ screenshot; showhud @showhud ]
+    showhud 0
+]
+
 listcomplete vdelta [
     "vshaderparam specscale"
     "vshaderparam parallaxscale"
diff --git a/config/usage.cfg b/config/usage.cfg
index 5cf28b6..825ce1b 100644
--- a/config/usage.cfg
+++ b/config/usage.cfg
@@ -1,8 +1,8 @@
 // sets descriptions for commands, variables and aliases
-// usage: setdesc name description fields 
+// usage: setdesc name description fields
 // fields are keywords to describe either
-//      the bits of a bitfield variable 
-//      or the arguments of a command 
+//      the bits of a bitfield variable
+//      or the arguments of a command
 setdesc "kick" "kicks a player from the server with an optional reason" "name/cn [text]"
 setdesc "allow" "sets a ban exemption for a player on the server" "name/cn"
 setdesc "ban" "bans a player from the server with an optional reason" "name/cn [text]"
@@ -126,7 +126,7 @@ setdesc "capturelimit" "number of captures required to end the round (and win) i
 setdesc "capturepoints" "points added to score for capturing flag" "value"
 setdesc "capturepickuppoints" "points added to score for picking up enemy flag" "value"
 setdesc "capturethreshold" "automatically drop the flag if the flag-carrier ^"warps^" more than this distance" "distance"
-setdesc "capturebuffing" "buff circumstances" "[near own flag at its base] [near own loose flag] [holding own flag] [near teammate holding own flag] [holding enemy flag] [near teammate holding enemy flag]" 
+setdesc "capturebuffing" "buff circumstances" "[near own flag at its base] [near own loose flag] [holding own flag] [near teammate holding own flag] [holding enemy flag] [near teammate holding enemy flag]"
 setdesc "capturebuffdelay" "buffed when guarding, and for this long after" "milliseconds"
 setdesc "capturebuffarea" "multiply affinity radius by this much for buff" "value"
 setdesc "capturebuffdamage" "multiply outgoing damage by this much when buffed" "value"
@@ -158,7 +158,7 @@ setdesc "bomberpickuppoints" "points added to score for picking up the bomb" "va
 setdesc "bomberholdpoints" "points added to score for each interval (hold)" "value"
 setdesc "bomberholdpenalty" "points subtracted for having the bomb fuse run out (hold)" "value"
 setdesc "bomberthreshold" "automatically drop the bomb if the bomb-carrier ^"warps^" more than this distance" "distance"
-setdesc "bomberbuffing" "buff circumstances" "[when near own base] [when holding bomb] [when holding bomb as the defenders (attack only)]" 
+setdesc "bomberbuffing" "buff circumstances" "[when near own base] [when holding bomb] [when holding bomb as the defenders (attack only)]"
 setdesc "bomberbuffdelay" "buffed for this long after leaving" "milliseconds"
 setdesc "bomberbuffarea" "multiply affinity radius by this much for buff" "value"
 setdesc "bomberbuffdamage" "multiply outgoing damage by this much when buffed" "value"
@@ -214,6 +214,7 @@ setdesc "impulseregenpacing" "impulse regen modifier when pacing" "value"
 setdesc "impulseregenmove" "impulse regen modifier when moving" "value"
 setdesc "impulseregeninair" "impulse regen modifier when in air" "value"
 setdesc "impulseregendelay" "delay before impulse regens" "value"
+setdesc "impulseregendelay" "delay before impulse regens" "value"
 setdesc "zoomlock" "0 = unrestricted, 1 = must be on floor, 2 = also must not be moving, 3 = also must be on flat floor, 4 = must also be crouched" "value"
 setdesc "zoomlocktime" "time before zoomlock kicks in when in the air" "milliseconds"
 setdesc "autoscores" "1 = when dead, 2 = also in spectv, 3 = and in waittv too" "value"
@@ -232,6 +233,7 @@ setdesc "inventorybgspace" "for aligning diagonals" "value"
 setdesc "inventoryhealth" "0 = off, 1 = text, 2 = bar, 3 = bar + text" "value"
 setdesc "inventoryhealthbartop" "starts from this offset" "value"
 setdesc "inventoryhealthbarbottom" "ends at this offset" "value"
+setdesc "inventoryinput" "a hud element to show key presses" "[show your own input] [show input of others in spectator mode]"
 setdesc "inventoryimpulsebartop" "starts from this offset" "value"
 setdesc "inventoryimpulsebarbottom" "ends at this offset" "value"
 setdesc "inventorystatus" "0 = off, 1 = text, 2 = icon, 3 = icon + tex" "value"
@@ -267,7 +269,7 @@ setdesc "obitverbose" "0 = extremely simple, 1 = simplified per-weapon, 2 = regu
 setdesc "obitstyles" " 0 = no obituary styles, 1 = show sprees/dominations/etc" "value"
 setdesc "showpresence" "0 = never show join/leave, 1 = show only during game, 2 = show when connecting/disconnecting" "value"
 setdesc "showteamchange" "0 = never show, 1 = show only when switching between, 2 = show when entering match too" "value"
-setdesc "deathanim" "0 = hide player when dead, 1 = old death animation, 2 = ragdolls" "value"
+setdesc "deathanim" "0 = hide player when dead, 1 = old death animation, 2 = ragdolls, 3 = ragdolls, but hide in duel/survivor" "value"
 setdesc "deathfade" "0 = don't fade out dead players, 1 = fade them out" "value"
 setdesc "deathscale" "0 = don't scale out dead players, 1 = scale them out" "value"
 setdesc "gravity" "gravity" "value"
@@ -277,7 +279,12 @@ setdesc "impulseaction" "determines how impulse action works, 0 = off, 1 = impul
 setdesc "impulseroll" "determines the camera angle tilt when wallrunning" "angle"
 setdesc "dashstyle" "0 = only with impulse, 1 = double tap" "value"
 setdesc "crouchstyle" "0 = press and hold, 1 = double-tap toggle, 2 = toggle" "value"
-setdesc "pacingstyle" "0 = press and hold, 1 = double-tap toggle, 2 = toggle, 3-5 = same, but auto engage if impulsepacing == 0" "value"
+setdesc "walkstyle" "0 = press and hold, 1 = double-tap toggle, 2 = toggle" "value"
+setdesc "kickoffstyle" "client side option for wall jumps. 0 = fixed angle, 1 = direction of view" ""
+setdesc "kickoffangle" "wall jump angle for kickoffstyle = 0" ""
+setdesc "kickupstyle" "client side option for wall kicks or climbing. 0 = fixed angle, 1 = direction of view" ""
+setdesc "kickupangle" "wall kick/climb angle for kickoffstyle = 0" ""
+setdesc "grabstyle" "setting for angle of parkour grabs; 0 = up and down ; 1 = inverted up and down ;  2 = only upwards, like wall kicks" ""
 setdesc "showentdir" "0 = off, 1 = only selected, 2 = always when editing, 3 = always in editmode" "value"
 setdesc "aboveheadblend" "determines the opacity of all abovehead* vars" "value"
 setdesc "aboveheaddamage" "shows amount of damage done to a player above their head" "value"
@@ -317,7 +324,7 @@ setdesc "inventoryimpulse" "sets how the impulse meter is displayed;^n0 = do not
 setdesc "impulsetex" "determines what image is used for the impulse meter" "value"
 setdesc "inventoryleft" "sets the size of the inventory on the left" "value"
 setdesc "inventoryright" "sets the size of the inventory on the right" "value"
-setdesc "inventorytone" "sets the colour tone of various inventory elements" "hex value"
+setdesc "inventorytone" "sets the colour tone of various inventory elements" "hexcolour"
 setdesc "inventoryweapids" "sets how to display the slot number of the weapons in the weapon bar;^n0 = do not show, 1 = show for active weapon only, 2 = show for all weapons" "value"
 setdesc "noticescale" "sets the size of the notices" "value"
 t = "Sets a colour according to the player's chosen profile colour and current team; ^n-1 = force profile colour, no special tones applied; ^n0 = team colour; ^n1 = profile colour; ^n2 = profile colour when neutral, team when teamed; ^n3 = neutral colour when neutral, profile when teamed; ^n4 = mix of profile and team colour; ^n5 = profile colour when neutral, mixed colour when teamed; ^n6 = mixed colour when neutral, profile colour when teamed"
@@ -410,7 +417,7 @@ setdesc "enemyskillmax" "maximum randomly assigned skill level for neutral actor
 setdesc "botlimit" "maximum number of bots allowed, regardless of any other variable/setting" "value"
 setdesc "botbalance" "determines bot balancing method;^n-1 = always use numplayers, 0 = don't balance, 1 or more = fill only with this*numteams" "value"
 looplist i [player bot drone grunt turret] [
-    setdesc (concatword $i abilities) (format "allowed actions for %1s." $i) "move jump crouch dash boost parkour melee  primary-fire secondary-fire push-/stunable affinity regen"
+    setdesc (concatword $i abilities) (format "allowed actions for %1s." $i) "move jump crouch dash boost parkour melee  primary-fire secondary-fire push-/stunable affinity regen claw"
     setdesc (concatword $i collide) "determines which targets can be attacked" "players bots enemies"
     setdesc (concatword $i teamdamage) "determines which targets can take team damage" "players bots enemies ghosts"
     setdesc (concatword $i health) "spawn health" "value"
@@ -495,7 +502,7 @@ setdesc "grassdist" "sets the distance at which grass becomes visible" "value"
 setdesc "calclight" "create and apply lightmap for all textures.^nquality option: 1 highest (default), 0 = custom (see lmaa and lmshadows), -1 = lowest^nquick option: 0 = use current lightprecision (default),  1 = use lightprecisionquick (very coarse)"  "quality quick"
 setdesc "patchlight" "calculate lightmap on newly edited cubes. New shadows on old cubes are not taken into account.^nquality option: 1 highest (default), 0 = custom (see lmaa and lmshadows), -1 = lowest^nquick option: 0 = use current lightprecision (default),  1 = use lightprecisionquick (very coarse)" "quality quick"
 setdesc "lmaa" "anti-aliasing option for lightmap with calclight 0 or patchlight 0^n 3 = 8x (default), 2 = 4x, 1 = 2x, 0 = 0ff" "value"
-setdesc "lmshadows" "shadow option for lightmap with calclight 0 or patchlight 0^n 2 = world and mapmodel shadows (default), 1 = wolrd shadows only, 0 = off" "value" 
+setdesc "lmshadows" "shadow option for lightmap with calclight 0 or patchlight 0^n 2 = world and mapmodel shadows (default), 1 = wolrd shadows only, 0 = off" "value"
 setdesc "quicklight" "preview lightmap for all textures using lightprecisionquick;^nquality: -1 to 1 (defaults to -1)" "quality"
 setdesc "quickpatchlight" "preview lightmap on newly edited cubes using lightprecisionquick;^nquality: -1 to 1 (defaults to -1)" "quality"
 setdesc "lightprecision" "set precision of lightmap, a higher value reduces quality and speeds up calclight" "value"
@@ -537,16 +544,16 @@ setdesc "guicount" "Returns the number of menus in the current gui stack." ""
 setdesc "cleargui" "Closes all open menus; If an integer argument is given, it goes back value steps in the stack of open menus." "[value]"
 setdesc "guitext" "creates a text element;^ntext can be a raw string or a variable,^n[icon] is the path to an image, example: ^"textures/chat^",^n[colour] is a hexadecimal colour code, example: 0xFF0000, ^n[iconcolour] acts on the icon just like colour acts on the text,^n[wrap] specifies a width limit, which allows the text to expand over several lines" "text [icon] [colour] [iconcolour] [wrap] [faded] [overlayicon] [overlaycolour]"
 setdesc "guibutton" "creates a text button;^nname refers to the button's variable name,^naction defines what the button does,^n[alt-act] defines what the button does when right-clicked,^n[icon] is the path to an image, example: ^"textures/chat^",^n[colour] is a hexadecimal colour code, example: 0xFF0000" "name action [alt-act] [icon] [colour] [iconcolour] [wrap] [faded] [overlayicon] [overlaycolour]"
-setdesc "guiimage" "creates a clickable image;^npath is the path to an image, example: ^"textures/chat^",^n[action] defines what clicking the image does,^n[scale] defines the scale of the image,^n[overlaid] whether or not the image is overlaid with guioverlaytex (true/false),^n[alt-path] alternate image to use if path cannot be loaded,^n[alt-act] defines what the image does when right clicked,^n[colour] is a hexadecimal colour code - useful to colorize icons, e.g. for weapons or teams" " [...]
+setdesc "guiimage" "creates a clickable image;^npath is the path to an image, example: ^"textures/chat^",^n[action] defines what clicking the image does,^n[scale] defines the scale of the image,^n[framed] whether or not the image is overlaid with guioverlaytex (true/false),^n[alt-path] alternate image to use if path cannot be loaded,^n[alt-act] defines what the image does when right clicked,^n[colour] is a hexadecimal colour code - useful to colorize icons, e.g. for weapons or teams^n[ov [...]
 setdesc "guicheckbox" "creates a checkbox;^nname a text string shown to the right of the checkbox,^nvariable refers to the alias/var that the checkbox controls,^n[on] is the value given to variable when the checkbox is on, and vice-versa for [off], if these are not specified, 1/0 is assumed,^n[onchange] is the action taken whenever the checkbox is toggled,^n[colour] is a hexadecimal colour code, example: 0xFF0000" "name var [on] [off] [onchange] [colour]"
 setdesc "guiradio" "creates a radio button;^nname a text string shown to the right of the radio button,^nvar refers to the alias/var that the radio button controls,^nvalue is the value given to var when selected,^n[onchange] is the action taken whenever the button is toggled,^n[colour] is a hexadecimal colour code, example: 0xFF0000" "name var value [onchange] [colour]"
 setdesc "guilist" "adds content to a list. Guilists can be nested to arrange gui elements horizontally and vertically.^nThis also allows to structure the layout in order to use guibars, guisliders and coloured guibackgrounds." "content"
 setdesc "guibody" "allows a list of gui elements to act like a single button;^ncontent is a block of code inside an implied guilist,^naction is the action taken when clicking any element of content^n[alt-act] allows to define a different right-click action,^n[onhover] is executed when the mouse moves over the boundary of the guibody - useful for guitooltip" "content action [alt-act] [onhover]"
 setdesc "guispring" "adds weight to a menu for pushing elements more/less to one side;^nexample: guilist [ guispring ; guitext ^"centered^"; guispring ]" "[value]"
-setdesc "guistrut" "adds spacing to a menu;^nif [bool] is 1, it wraps the guistrut in a guilist (adds spacing in the alternate direction),^n^"guistrut 5 1^" is the same as ^"guilist [ guistrut 5 ]^"" "amount [bool]"
-setdesc "guibar" "draws either a vertical or a horizontal line, depending on the nesting of guilist" "[size] [space] [colour] [border]"
+setdesc "guistrut" "adds horizontal or vertical spacing, measured in units of the current font width or height, respectively;^nthe direction depends on the nesting of guilist, and can be switched with the second argument;^nfor example, ^"guistrut 5 1^" is the same as ^"guilist [ guistrut 5 ]^"" "amount [bool]"
+setdesc "guibar" "draws either a vertical or a horizontal line, depending on the nesting of guilist^nthe fill and border colours are hexadecimals, e.g 0xffffff" "[width] [spacing] [colour] [border]"
 setdesc "guifont" "sets the text font to be used in a block;^nfont is a keyword, e.g. emphasis or small^nblock is a block of gui elements for which font is used^nexample: newgui font-preview [loopfiles i config/fonts cfg [guifont $i [guitext $i]]]^nnote: This will preview all of the available font options." "font block"
-setdesc "guibackground" "creates a colored background for the current guilist;^ncolour is in hexadecimal, example: 0xFF0000,^n[blend] is the opacity of the colour, ranges from 0 (invisible) to 1 (opaque),^n[bordercolour] is the colour of the border,^n[borderblend] is the opacity of the border,^n[border] determines if the border is drawn (0 or 1),^n[levels] specifies how many guilist levels to go back" "colour [blend] [bordercolour] [borderblend] [border]  [levels]"
+setdesc "guibackground" "creates a colored background and border for the current guilist; ^ncolour is hexadecimal, e.g. 0xffaa00, use -1 for the gui default,^n[blend] is the opacity of the colour, ranges from 0 (invisible) to 1 (opaque),^n[bordercolour] is the colour of the border,^n[borderblend] is the opacity of the border,^n[border] determines if the border is drawn (0 or 1),^n[levels] specifies how many guilist levels to go back" "colour [blend] [bordercolour] [borderblend] [border]  [...]
 setdesc "guifill" "creates a coloured background for the current guilist, but without the skin settings of guibackground.^nThis implies sharp corners, no borders and a blend value of 0.5;^ncolour is in hexadecimal, example: 0xFF0000,^n[levels] specifies how many guilist levels to go back" "colour [levels]"
 setdesc "guifield" "creates a text field;^nvar is the variable the guifield controls,^nmaxlength is the width in characters, negative values allow the field to expand downward,^n[onchange] is the action taken when the value changes,^n[colour] is a hex colour, e.g. 0xFF0000, ^n[focus] (1) will keep the input field in focus,^n[parent] allows to pass the input,^n[height] is the number of lines to show with a scroll bar,^n[promt] is a text shown when var is empty,^n[immediate] (1) updates va [...]
 setdesc "guieditor" "creates a guifield with content from a text file;^nname is a filename,^n[maxlength] is the width of the field, negative values imply text wrap,^n[height] is the number of lines shown with a scroll bar,^n[mode] (4) implies read-only,^n[colour] is a hex code for the entire editor,^n[focus] (1) keeps the editor field in focus^n[parent] allows to pass input,^n[str] allows to do a textinit using name,^n[prompt] is shown when the field is empty." "name [maxlength] [height] [...]
@@ -564,7 +571,33 @@ setdesc "guitooltip" "creates a text box attached to the mouse pointer;^ntext is
 setdesc "guirollovername" "Returns the text label or image path of the gui element at the current mouse position." ""
 setdesc "guirolloveraction" "Returns the action of a button or image or the var of a checkbox or radio at the current mouse position." ""
 setdesc "guirollovertype" "Returns the type of the gui element at the current mouse position, e.g. text, image or checkbox." ""
-
+//gui glue
+setdesc "guitest" "creates and shows a test menu, with button actions shown in the status bar;^n This is useful for quickly testing some gui commands." "content"
+setdesc "guitip" "shows a tip in the status bar. Shows a random tip if no content is given" "content"
+setdesc "guispacing" "adds horizontal and vertical spacing;^nthis is for even nesting (or absence) of guilist;^nsee also: guispacing2, guistrtut" "width height"
+setdesc "guispacing2" "adds horizontal and vertical spacing;^nthis is for odd nesting of guilist;^nsee also: guispacing, guistrtut" "width height"
+setdesc "guilist2" "forms a block with two nested guilists" "content"
+setdesc "guiright" "a guilist for right adjusted content" "content"
+setdesc "guicenter" "a guilist for centered content" "content"
+setdesc "guicenterz" "two nested guilists for centered content" "content"
+setdesc "guibox" "creates a framed box with the given background and border colour. Use hex codes, or -1 for the skin defaults.^nsee also: guibox2, guibackground" "bgcolour bordercolour content"
+setdesc "guibox2" "guibox for odd nesting of guilist" "bgcolour bordercolour content"
+setdesc "guimerge" "merges elements into a button, similar to guibody, but with a fixed width" "width content action alt-action tooltip"
+setdesc "guiloopsplit" "creates multiple columns using a loop, optionaly with extra content after the first column" "loopvar columns loopmax content extra"
+setdesc "guilooplistsplit" "similar to guilistsplit, but using looplist instead of loop" "loopvar columns list content [extra]"
+setdesc "guiloopscrollbar" "creates a block with a scrollbar, using content in a loop" "loopvar loopmax lines width content"
+setdesc "guicheckbox2" "a checkbox with three states" "label var"
+setdesc "guiradio2" "much like guiradio, but only works with game variables" "label var value"
+setdesc "guieditbutton" "button with built-in editbind lookup, tooltip and console alt action;^nsee also: guieditbutton2" "text action"
+setdesc "guieditbutton2" "guieditbutton for odd nesting of guilist" "text action"
+setdesc "guieditcheckbox" "a checkbox for looking up editvarbinds, with tooltip and alt-action;^nsee also: guieditcheckbox2" "text variable"
+setdesc "guieditcheckbox2" "guieditcheckbox for odd nesting of guilist" "text variable"
+setdesc "guiswitch" "a double guilist with either of two contents" "condition true-content false-content"
+setdesc "guione" "small macro with one centered block" "content"
+setdesc "guitwo" "small macro with three panels, left, right and bottom right" "left right bottom-right"
+setdesc "guibigmacro" "big scrollbar loop macro used in the menus for servers, votes and favorites" "content"
+setdesc "gui" "" "content"
+// aliases and input 
 setdesc "setpersist" "toggles an alias as persistent, that is, determines if it will be added to config.cfg;^nbool: 0 = not persistent, 1 = persistent" "alias bool"
 setdesc "local" "declares a set of aliases as local to the alias (function) it was declared in"
 setdesc "alias" "defines a new alias (variable or command block);^n example: alias pi 3.14159265359^nis equivalent to: pi = 3.14159265359" "name contents"
@@ -779,8 +812,8 @@ setdesc "autospecdelay" "determines the delay after player joins spectators afte
 setdesc "bomberlockondelay" "determines the amount of time needed to hold throw button to achieve assisted pass to another team member" "milliseconds"
 setdesc "capturecarryspeed" "multiplier for the movement speed of flag-carrying players;^nacts as an added multiplier on top of the normal movespeed multiplier" "value"
 setdesc "capturepickupdelay" "time a player needs to wait to be able to pick up a flag again after losing it" "milliseconds"
-setdesc "coopskillmax" "determines the maximum skill level a bot can have in coop" "skill level"
-setdesc "coopskillmin" "determines the minimum skill level a bot can have in coop" "skill level"
+setdesc "coopskillmax" "determines the maximum skill level a bot can have in coop" "value"
+setdesc "coopskillmin" "determines the minimum skill level a bot can have in coop" "value"
 setdesc "fragbonus" "determines the amount of points a player gets for each frag" "value"
 setdesc "enemybonus" "determines the amount of points a player gets for frags against neutral enemies in onslaught" "value"
 setdesc "headshotpoints" "determines the amount of bonus points a player gets when a frag is caused by a headshot" "value"
@@ -841,14 +874,14 @@ looplist w $weapname [
         setdesc (concatword $w fragrays $m) "when creating flak, create this many projectiles for it" "value"
         setdesc (concatword $w fragtime $m) "flak projectiles from this weapon last this long" "milliseconds"
         setdesc (concatword $w fragspeed $m) "flak projectiles from this weapon start with this much speed (may be influenced by fragspread/fragskew and fragrel)" "value"
-        setdesc (concatword $w collide $m) "determines collision properties for a projectile from each firing action (bitwise OR)" "COLLIDE_TRACE COLLIDE_PROJ COLLIDE_OWNER IMPACT_GEOM IMPACT_PLAYER IMPACT_SHOTS BOUNCE_GEOM BOUNCE_PLAYER BOUNCE_SHOTS DRILL_GEOM DRILL_PLAYER DRILL_SHOTS STICK_GEOM STICK_PLAYER COLLIDE_HITSCAN" 
+        setdesc (concatword $w collide $m) "determines collision properties for a projectile from each firing action (bitwise OR)" "COLLIDE_TRACE COLLIDE_PROJ COLLIDE_OWNER IMPACT_GEOM IMPACT_PLAYER IMPACT_SHOTS BOUNCE_GEOM BOUNCE_PLAYER BOUNCE_SHOTS DRILL_GEOM DRILL_PLAYER DRILL_SHOTS STICK_GEOM STICK_PLAYER COLLIDE_HITSCAN"
         setdesc (concatword $w extinguish $m) "determines if a projectile from each firing action is extinguished by water" "value"
         setdesc (concatword $w cooked $m) "determines cooking style for a projectile" "[more damage] [less damage] [longer lifetime] [shorter lifetime] [faster speed] [slower speed] [extra rays] [zoom view] [keep zoom view]"
         setdesc (concatword $w guided $m) "determines guided style for a projectile, 0 = off, 1 = follow crosshair direction, 2 = home crosshair target (+1 only first target), 4 = home projectile target (+1 only first target), 6 = direct toward first crosshair direction" "value"
         setdesc (concatword $w radial $m) "determines the time between ticks of ^"continuous^" radial damage, starts counting from first ^"radiation^"" "milliseconds"
 
         setdesc (concatword $w residual $m) "determines if a projectile from each firing action has a residual effect" "burn bleed shock"
-        setdesc (concatword $w residualundo $m) "determines if a projectile from each firing action will extinguish a residual effect" "burn bleed shock" 
+        setdesc (concatword $w residualundo $m) "determines if a projectile from each firing action will extinguish a residual effect" "burn bleed shock"
         setdesc (concatword $w fullauto $m) "determines if each firing action is fully automatic (click-and-hold) or not (click-and-click)" "bool"
         setdesc (concatword $w taperin $m) "determines the fraction of the lifetime from the start until the projectile reaches its full size" "value"
         setdesc (concatword $w taperout $m) "determines the fraction of the lifetime from the end where the projectile size ^"fades out^"" "value"
@@ -882,5 +915,6 @@ looplist w $weapname [
         setdesc (concatword $w partcol $m) "determines the colour of a projectile from this weapon" "colour"
         setdesc (concatword $w parttype $m) "determines the type of projectiles this weapon shoots" "value"
         setdesc (concatword $w residual $m) "determines the residual effects of a projectile from each firing action (bitwise OR)" "burns bleeds shocks"
+        setdesc (concatword $w grab $m) "determines if the weapon is used for parkour grabs" "[can grab on walls] [can grab players]"
     ]
 ]
diff --git a/sql/stats/create.sql b/sql/stats/create.sql
index 07a8eac..3b67e45 100644
--- a/sql/stats/create.sql
+++ b/sql/stats/create.sql
@@ -9,7 +9,8 @@ CREATE TABLE games (
     mode INTEGER,
     mutators INTEGER,
     timeplayed INTEGER,
-    uniqueplayers INTEGER
+    uniqueplayers INTEGER,
+    usetotals INTEGER
 );
 
 CREATE TABLE game_servers (
diff --git a/src/engine/client.cpp b/src/engine/client.cpp
index df94b97..d4ac25b 100644
--- a/src/engine/client.cpp
+++ b/src/engine/client.cpp
@@ -83,13 +83,13 @@ void connectfail()
     localconnect(false);
 }
 
-void trydisconnect()
+void trydisconnect(bool force)
 {
     if(connpeer) abortconnect();
     else if(curpeer || haslocalclients())
     {
         if(verbose) conoutft(CON_MESG, "\faattempting to disconnect...");
-        disconnect(0, !discmillis);
+        disconnect(false, !force && !discmillis);
     }
     else conoutft(CON_MESG, "\frnot connected");
 }
@@ -155,9 +155,9 @@ void connectserv(const char *name, int port, const char *password)
     conoutft(CON_MESG, "\fgconnecting to %s:[%d]", name != NULL ? name : "local server", port);
 }
 
-void disconnect(int onlyclean, int async)
+void disconnect(bool onlyclean, bool async)
 {
-    bool cleanup = onlyclean!=0;
+    bool cleanup = onlyclean;
     if(curpeer || haslocalclients())
     {
         if(curpeer)
@@ -193,7 +193,7 @@ void disconnect(int onlyclean, int async)
 }
 
 ICOMMAND(0, connect, "sis", (char *n, int *a, char *pwd), connectserv(*n ? n : servermaster, *n || *a ? *a : SERVER_PORT, pwd));
-COMMANDN(0, disconnect, trydisconnect, "");
+ICOMMAND(0, disconnect, "i", (int *force), trydisconnect(*force!=0));
 
 ICOMMAND(0, lanconnect, "is", (int *a, char *pwd), connectserv(NULL, *a, pwd));
 ICOMMAND(0, localconnect, "i", (int *n), localconnect(*n ? false : true));
@@ -207,7 +207,7 @@ void reconnect(const char *pass)
     string addr = "";
     if(*connectname) copystring(addr, connectname);
     if(connectport) port = connectport;
-    disconnect(1);
+    disconnect(true);
     connectserv(*addr ? addr : NULL, port > 0 ? port : SERVER_PORT, pass);
 }
 COMMAND(0, reconnect, "s");
diff --git a/src/engine/command.cpp b/src/engine/command.cpp
index 8cdf912..27b76a5 100644
--- a/src/engine/command.cpp
+++ b/src/engine/command.cpp
@@ -1245,6 +1245,7 @@ done:
     }
     return;
 invalid:
+    delete[] lookup;
     switch(ltype)
     {
         case VAL_NULL: case VAL_ANY: compilenull(code); break;
diff --git a/src/engine/decal.cpp b/src/engine/decal.cpp
index ce9d96c..1a29611 100644
--- a/src/engine/decal.cpp
+++ b/src/engine/decal.cpp
@@ -308,6 +308,8 @@ struct decalrenderer
 
     void adddecal(const vec &center, const vec &dir, float radius, const bvec &color, int info)
     {
+        if(dir.iszero()) return;
+
         int bbradius = int(ceil(radius));
         bbmin = ivec(center).sub(bbradius);
         bbmax = ivec(center).add(bbradius);
diff --git a/src/engine/irc.cpp b/src/engine/irc.cpp
index e07fa6e..900c666 100644
--- a/src/engine/irc.cpp
+++ b/src/engine/irc.cpp
@@ -50,7 +50,7 @@ void ircprintf(ircnet *n, int relay, const char *target, const char *msg, ...)
     {
         formatstring(s, "\fs\fa[%s]\fS", n->name);
         #ifndef STANDALONE
-        n->buffer.addline(newstring(str), MAXIRCLINES);
+        n->buffer.addline(str, MAXIRCLINES);
         n->updated |= IRCUP_MSG;
         #endif
     }
@@ -523,7 +523,7 @@ void ircprocess(ircnet *n, char *user[3], int g, int numargs, char *w[])
                             ircprintf(n, 4, g ? w[g+1] : NULL, "\fr%s requests: %s %s", user[0], q, r);
 
                             if(!strcasecmp(q, "VERSION"))
-                                ircsend(n, "NOTICE %s :\vVERSION %s v%s-%s%d-%s (%s)%s%s\v", user[0], VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE, *VERSION_URL ? ", " : "", VERSION_URL);
+                                ircsend(n, "NOTICE %s :\vVERSION %s v%s-%s%d-%s (%s)%s%s\v", user[0], versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease, *versionurl ? ", " : "", versionurl);
                             else if(!strcasecmp(q, "PING")) // eh, echo back
                                 ircsend(n, "NOTICE %s :\vPING %s\v", user[0], r);
                         }
@@ -809,7 +809,7 @@ void irccleanup()
     loopv(ircnets) if(ircnets[i]->sock != ENET_SOCKET_NULL)
     {
         ircnet *n = ircnets[i];
-        ircsend(n, "QUIT :%s%s%s", VERSION_NAME, *VERSION_URL ? ", " : "", VERSION_URL);
+        ircsend(n, "QUIT :%s%s%s", versionname, *versionurl ? ", " : "", versionurl);
         ircdiscon(n, "shutdown");
     }
 }
@@ -921,7 +921,7 @@ void ircslice()
                     copystring(n->nick, n->mnick);
                     ircsend(n, "NICK %s", n->mnick);
                     if(!*n->ident) copystring(n->ident, systemuser);
-                    ircsend(n, "USER %s +iw %s :%s v%s-%s%d-%s (%s)", n->ident, n->ident, VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE);
+                    ircsend(n, "USER %s +iw %s :%s v%s-%s%d-%s (%s)", n->ident, n->ident, versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease);
                     n->lastnick = clocktime;
                     n->state = IRC_CONN;
                     loopvj(n->channels)
@@ -1026,8 +1026,8 @@ void irccmd(ircnet *n, ircchan *c, char *s)
             {
                 if(c)
                 {
-                    ircsend(n, "PRIVMSG %s :%s v%s-%s%d-%s (%s); %s (%s v%s)", c->name, VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE, gfxrenderer, gfxvendor, gfxversion);
-                    ircprintf(n, 1, c->name, "\fw<%s> %s v%s-%s%d-%s (%s); %s (%s v%s)", n->nick, VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE, gfxrenderer, gfxvendor, gfxversion);
+                    ircsend(n, "PRIVMSG %s :%s v%s-%s%d-%s (%s); %s (%s v%s)", c->name, versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease, gfxrenderer, gfxvendor, gfxversion);
+                    ircprintf(n, 1, c->name, "\fw<%s> %s v%s-%s%d-%s (%s); %s (%s v%s)", n->nick, versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease, gfxrenderer, gfxvendor, gfxversion);
                 }
                 else ircprintf(n, 4, NULL, "\fyyou are not on a channel");
             }
diff --git a/src/engine/main.cpp b/src/engine/main.cpp
index c3ec1d0..a81919e 100644
--- a/src/engine/main.cpp
+++ b/src/engine/main.cpp
@@ -12,7 +12,7 @@ void setcaption(const char *text, const char *text2)
     {
         copystring(prevtext, text);
         copystring(prevtext2, text2);
-        formatstring(caption, "%s v%s-%s%d-%s (%s)%s%s%s%s", VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE, text[0] ? ": " : "", text, text2[0] ? " - " : "", text2);
+        formatstring(caption, "%s v%s-%s%d-%s (%s)%s%s%s%s", versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease, text[0] ? ": " : "", text, text2[0] ? " - " : "", text2);
         if(screen) SDL_SetWindowTitle(screen, caption);
     }
 }
@@ -112,7 +112,7 @@ void quit()                  // normal exit
     writeservercfg();
     writecfg();
     abortconnect();
-    disconnect(1);
+    disconnect(true);
     cleanup();
     exit(EXIT_SUCCESS);
 }
@@ -140,7 +140,7 @@ void fatal(const char *s, ...)    // failure exit
                 #endif
             }
             SDL_Quit();
-            defformatstring(cap, "%s: Error", VERSION_NAME);
+            defformatstring(cap, "%s: Error", versionname);
             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, cap, msg, NULL);
         }
     }
@@ -715,7 +715,7 @@ void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep)
     EXCEPTION_RECORD *er = ep->ExceptionRecord;
     CONTEXT *context = ep->ContextRecord;
     bigstring out;
-    formatstring(out, "%s Win32 Exception: 0x%x [0x%x]\n\n", VERSION_NAME, er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
+    formatstring(out, "%s Win32 Exception: 0x%x [0x%x]\n\n", versionname, er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
     SymInitialize(GetCurrentProcess(), NULL, TRUE);
 #ifdef _AMD64_
     STACKFRAME64 sf = {{context->Rip, 0, AddrModeFlat}, {}, {context->Rbp, 0, AddrModeFlat}, {context->Rsp, 0, AddrModeFlat}, 0};
diff --git a/src/engine/master.cpp b/src/engine/master.cpp
index 2d3b1a5..c8dc15b 100644
--- a/src/engine/master.cpp
+++ b/src/engine/master.cpp
@@ -492,7 +492,7 @@ void savestats(masterclient &c)
 
     statsdbexecf("COMMIT");
     conoutf("master peer %s commited stats, game id %lu", c.name, c.stats.id);
-    masteroutf(c, "stats success \"game statistics recorded, id \fc%lu\"\n", c.stats.id);
+    masteroutf(c, "stats success \"game statistics recorded (\fs\fy%lu\fS) \fs\fc%sstats/game/%lu\fS\"\n", c.stats.id, versionurl, c.stats.id);
     c.instats = false;
     c.wantstats = false;
 }
diff --git a/src/engine/movie.cpp b/src/engine/movie.cpp
index fcdbf4e..3ec9c61 100644
--- a/src/engine/movie.cpp
+++ b/src/engine/movie.cpp
@@ -373,7 +373,7 @@ struct aviwriter
         endlistchunk(); // LIST odml
 
         listchunk("LIST", "INFO");
-        const char *software = VERSION_NAME;
+        const char *software = versionname;
         writechunk("ISFT", software, strlen(software)+1);
         endlistchunk(); // LIST INFO
 
diff --git a/src/engine/rendergl.cpp b/src/engine/rendergl.cpp
index 8ac0b8a..61513af 100644
--- a/src/engine/rendergl.cpp
+++ b/src/engine/rendergl.cpp
@@ -2110,7 +2110,7 @@ void drawviewtype(int targtype)
     visiblecubes();
 
     glClearColor(0, 0, 0, 0);
-    glClear(GL_DEPTH_BUFFER_BIT | (clearview(viewtype, targtype) ? GL_COLOR_BUFFER_BIT : 0));
+    glClear(GL_DEPTH_BUFFER_BIT | (wireframe && editmode && clearview(viewtype, targtype) ? GL_COLOR_BUFFER_BIT : 0));
 
     if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 
diff --git a/src/engine/renderparticles.cpp b/src/engine/renderparticles.cpp
index a1ab207..0538323 100644
--- a/src/engine/renderparticles.cpp
+++ b/src/engine/renderparticles.cpp
@@ -336,17 +336,17 @@ struct textrenderer : sharedlistrenderer
             if(*text) { int len = text-(start+1); memcpy(font, start+1, len); font[len] = '\0'; text++; }
             else text = start;
         }
-        float scale = p->size/80.0f, xoff = -text_width(text)/2;
+        pushfont(*font ? font : "default");
+        float scale = p->size/80.0f, xoff = -text_widthf(text)*0.5f;
 
         matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), p->o);
         m.scale(scale);
         m.translate(xoff, 0, 50);
 
         textmatrix = &m;
-        pushfont(*font ? font : "default");
         draw_text(text, 0, 0, p->color.r, p->color.g, p->color.b, int(p->blend*blend));
-        popfont();
         textmatrix = NULL;
+        popfont();
     }
 };
 static textrenderer texts, textontop(PT_ONTOP);
diff --git a/src/engine/rendersky.cpp b/src/engine/rendersky.cpp
index 2501ac9..b2a5363 100644
--- a/src/engine/rendersky.cpp
+++ b/src/engine/rendersky.cpp
@@ -516,7 +516,6 @@ void drawskybox(int farplane, bool limited)
     if((!glaring || skybgglare) && blendsky)
     {
         SETSHADER(skyfog);
-        notextureshader->set();
 
         matrix4 skymatrix = cammatrix, skyprojmatrix;
         skymatrix.settranslation(0, 0, 0);
@@ -524,7 +523,7 @@ void drawskybox(int farplane, bool limited)
         LOCALPARAM(skymatrix, skyprojmatrix);
 
         gle::color(vec::hexcolor(skybgcolour));
-        drawenvboxbg(farplane/2); //, skyclip, topclip, yawskyfaces(renderedskyfaces, yawsky, spinsky));
+        drawenvboxbg(farplane/2, skyclip, topclip, yawskyfaces(renderedskyfaces, yawsky, spinsky));
     }
 
     if(glaring) SETSHADER(skyboxglare);
diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp
index ecf461a..c0eca72 100644
--- a/src/engine/rendertext.cpp
+++ b/src/engine/rendertext.cpp
@@ -10,10 +10,11 @@ VAR(IDF_PERSIST, textminintensity, 0, 32, 255);
 VAR(IDF_PERSIST, textkeyimages, 0, 1, 1);
 FVAR(IDF_PERSIST, textkeyimagescale, 0, 1, FVAR_MAX);
 VAR(IDF_PERSIST, textkeyseps, 0, 1, 1);
+VAR(IDF_PERSIST|IDF_HEX, textkeycolour, 0, 0x00FFFF, 0xFFFFFF);
 
 Texture *tbgbordertex = NULL, *tbgtex = NULL;
 VAR(IDF_PERSIST, textskin, 0, 2, 2);
-VAR(IDF_PERSIST, textskinsize, 0, 96, VAR_MAX);
+VAR(IDF_PERSIST, textskinsize, 0, 48, VAR_MAX);
 FVAR(IDF_PERSIST, textskinblend, 0, 0.3f, 1);
 FVAR(IDF_PERSIST, textskinfblend, 0, 1, 1);
 FVAR(IDF_PERSIST, textskinbright, 0, 0.6f, 10);
@@ -696,6 +697,8 @@ static const char *gettklp(const char *str)
     return t->blist.search(str, type, "", "", " ", " ", 5);
 }
 
+#define defformatkey(dest, key) defformatbigstring((dest), "\fs\fa[\fS\fs\f[%d]%s\fS\fs\fa]\fS", textkeycolour, (key))
+
 float key_widthf(const char *str)
 {
     const char *keyn = str;
@@ -716,7 +719,8 @@ float key_widthf(const char *str)
             }
             // fallback if not found
         }
-        width += text_widthf(list[i]);
+        defformatkey(keystr, list[i]);
+        width += text_widthf(keystr);
     }
     list.deletearrays();
     return width;
@@ -759,7 +763,9 @@ static int draw_key(Texture *&tex, const char *str, float sx, float sy)
             tex = oldtex;
             glBindTexture(GL_TEXTURE_2D, tex->id);
         }
-        width += draw_textf("\fs\fa[\fS%s\fs\fa]\fS", sx + width, sy, 0, 0, 255, 255, 255, 255, 0, -1, -1, 1, list[i]);
+        defformatkey(keystr, list[i]);
+        draw_text(keystr, sx + width, sy, 255, 255, 255, 255, 0, -1, -1, 1, -1);
+        width += text_widthf(keystr);
     }
     list.deletearrays();
     return width;
diff --git a/src/engine/server.cpp b/src/engine/server.cpp
index c05444c..0168047 100644
--- a/src/engine/server.cpp
+++ b/src/engine/server.cpp
@@ -1241,7 +1241,7 @@ static void setupwindow(const char *title)
     atexit(cleanupwindow);
 
     if(!setupsystemtray(WM_APP)) fatal("failed adding to system tray");
-    conoutf("identity: %s@%s v%s-%s%d-%s %s (%s) [0x%.8x]", systemuser, systemhost, VERSION_STRING, versionplatname, versionarch, versionbranch, versionisserver ? "server" : "client", VERSION_RELEASE, versioncrc);
+    conoutf("identity: %s@%s v%s-%s%d-%s %s (%s) [0x%.8x]", systemuser, systemhost, versionstring, versionplatname, versionarch, versionbranch, versionisserver ? "server" : "client", versionrelease, versioncrc);
 }
 
 static char *parsecommandline(const char *src, vector<char *> &args)
@@ -1311,7 +1311,7 @@ void logoutfv(const char *fmt, va_list args)
 void serverloop()
 {
 #ifdef WIN32
-    defformatstring(cap, "%s server", VERSION_NAME);
+    defformatstring(cap, "%s server", versionname);
     setupwindow(cap);
     SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
 #endif
@@ -1437,7 +1437,7 @@ void setupserver()
 
 void initgame()
 {
-    conoutf("identity: %s@%s v%s-%s%d-%s %s (%s) [0x%.8x]", systemuser, systemhost, VERSION_STRING, versionplatname, versionarch, versionbranch, versionisserver ? "server" : "client", VERSION_RELEASE, versioncrc);
+    conoutf("identity: %s@%s v%s-%s%d-%s %s (%s) [0x%.8x]", systemuser, systemhost, versionstring, versionplatname, versionarch, versionbranch, versionisserver ? "server" : "client", versionrelease, versioncrc);
     server::start();
     loopv(gameargs)
     {
@@ -1594,7 +1594,7 @@ void setlocations(bool wanthome)
         string dir = "";
         if(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir) == S_OK)
         {
-            defformatstring(s, "%s\\My Games\\%s", dir, VERSION_NAME);
+            defformatstring(s, "%s\\My Games\\%s", dir, versionname);
             sethomedir(s);
         }
 #elif defined(__APPLE__)
@@ -1602,14 +1602,14 @@ void setlocations(bool wanthome)
         const char *dir = mac_personaldir(); // typically  /Users/<name>/Application Support/
         if(dir && *dir)
         {
-            defformatstring(s, "%s/%s", dir, VERSION_NAME);
+            defformatstring(s, "%s/%s", dir, versionname);
             sethomedir(s);
         }
 #else
         const char *dir = getenv("HOME");
         if(dir && *dir)
         {
-            defformatstring(s, "%s/.%s", dir, VERSION_UNAME);
+            defformatstring(s, "%s/.%s", dir, versionuname);
             sethomedir(s);
         }
 #endif
@@ -1787,7 +1787,7 @@ void fatal(const char *s, ...)    // failure exit
             cleanupserver();
             enet_deinitialize();
 #ifdef WIN32
-            defformatstring(cap, "%s: Error", VERSION_NAME);
+            defformatstring(cap, "%s: Error", versionname);
             MessageBox(NULL, msg, cap, MB_OK|MB_SYSTEMMODAL);
 #endif
         }
diff --git a/src/engine/sound.cpp b/src/engine/sound.cpp
index 85e70c4..2283fae 100644
--- a/src/engine/sound.cpp
+++ b/src/engine/sound.cpp
@@ -736,7 +736,7 @@ void initmumble()
         if(mumblelink)
         {
             mumbleinfo = (MumbleInfo *)MapViewOfFile(mumblelink, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(MumbleInfo));
-            if(mumbleinfo) wcsncpy(mumbleinfo->name, (const wchar_t *)VERSION_UNAME, 256);
+            if(mumbleinfo) wcsncpy(mumbleinfo->name, (const wchar_t *)versionuname, 256);
         }
     #elif _POSIX_SHARED_MEMORY_OBJECTS > 0
         defformatstring(shmname, "/MumbleLink.%d", getuid());
@@ -744,7 +744,7 @@ void initmumble()
         if(mumblelink >= 0)
         {
             mumbleinfo = (MumbleInfo *)mmap(NULL, sizeof(MumbleInfo), PROT_READ|PROT_WRITE, MAP_SHARED, mumblelink, 0);
-            if(mumbleinfo != (MumbleInfo *)-1) wcsncpy(mumbleinfo->name, (const wchar_t *)VERSION_UNAME, 256);
+            if(mumbleinfo != (MumbleInfo *)-1) wcsncpy(mumbleinfo->name, (const wchar_t *)versionuname, 256);
         }
     #endif
     if(!VALID_MUMBLELINK) closemumble();
diff --git a/src/engine/ui.cpp b/src/engine/ui.cpp
index 59cdc33..8557a5b 100644
--- a/src/engine/ui.cpp
+++ b/src/engine/ui.cpp
@@ -14,7 +14,7 @@ static int fieldmode = FIELDSHOW;
 static bool fieldsactive = false;
 
 FVAR(IDF_PERSIST, guiscale, FVAR_NONZERO, 0.00055f, VAR_MAX);
-VAR(IDF_PERSIST, guiskinsize, 0, 96, VAR_MAX); // 0 = texture size, otherwise = size in pixels for skin scaling
+VAR(IDF_PERSIST, guiskinsize, 0, 48, VAR_MAX); // 0 = texture size, otherwise = size in pixels for skin scaling
 VAR(IDF_PERSIST, guislidersize, 1, 58, VAR_MAX);
 VAR(IDF_PERSIST, guisepsize, 1, 6, VAR_MAX);
 VAR(IDF_PERSIST, guispacesize, 1, 48, VAR_MAX);
@@ -1039,7 +1039,7 @@ struct gui : guient
         drawslice(start, end, x+s/2, y+s/2, s);
         if(text && *text)
         {
-            int w = text_width(text, 0, 0, TEXT_NO_INDENT);
+            int w = int(ceil(text_widthf(text, 0, 0, TEXT_NO_INDENT)));
             text_(text, x+s/2-w/2, y+s/2-FONTH/2, 0xFFFFFF, guitextblend);
         }
     }
diff --git a/src/engine/version.h b/src/engine/version.h
index f75397a..757ac6a 100644
--- a/src/engine/version.h
+++ b/src/engine/version.h
@@ -1,7 +1,7 @@
 #define VERSION_MAJOR 1
 #define VERSION_MINOR 5
-#define VERSION_PATCH 5
-#define VERSION_STRING "1.5.5"
+#define VERSION_PATCH 6
+#define VERSION_STRING "1.5.6"
 #define VERSION_NAME "Red Eclipse"
 #define VERSION_UNAME "redeclipse"
 #define VERSION_VNAME "REDECLIPSE"
diff --git a/src/engine/worldio.cpp b/src/engine/worldio.cpp
index ccce3e8..89c4687 100644
--- a/src/engine/worldio.cpp
+++ b/src/engine/worldio.cpp
@@ -84,7 +84,7 @@ void setnames(const char *fname, int type, int crc)
     formatstring(mf, "%s%s", mapname, mapexts[maptype].name);
     setsvar("mapfile", mf);
 
-    conoutf("set map name to %s (%s)", mapname, mapfile);
+    if(verbose >= 1) conoutf("set map name to %s (%s)", mapname, mapfile);
 }
 
 enum { OCTSAV_CHILDREN = 0, OCTSAV_EMPTY, OCTSAV_SOLID, OCTSAV_NORMAL, OCTSAV_LODCUBE };
@@ -837,7 +837,7 @@ void save_config(char *mname)
     if(*mapauthor) filterstring(author, mapauthor);
     else copystring(author, mapauthor);
     // config
-    h->printf("// %s by %s (%s)\n// Config generated by %s\n\n", title, author, mapname, VERSION_NAME);
+    h->printf("// %s by %s (%s)\n// Config generated by %s\n\n", title, author, mapname, versionname);
 
     int vars = 0;
     h->printf("// Variables stored in map file, may be uncommented here, or changed from editmode.\n");
@@ -986,6 +986,7 @@ void save_world(const char *mname, bool nodata, bool forcesave)
             if(verbose) conoutf("saved text %s", fname);
         }
     }
+    game::savemap(forcesave, mapname);
 
     int numvslots = vslots.length();
     if(!nodata && !multiplayer(false))
@@ -1267,7 +1268,7 @@ bool load_world(const char *mname, int crc)       // still supports all map form
 
             if(newhdr.version > MAPVERSION)
             {
-                conoutf("\frerror loading %s: requires a newer version of %s", mapname, VERSION_NAME);
+                conoutf("\frerror loading %s: requires a newer version of %s", mapname, versionname);
                 delete f;
                 maploading = 0;
                 maskpackagedirs(mask);
diff --git a/src/game/ai.cpp b/src/game/ai.cpp
index 3e1aafc..16a280c 100644
--- a/src/game/ai.cpp
+++ b/src/game/ai.cpp
@@ -10,6 +10,7 @@ namespace ai
 
     VARF(0, showwaypoints, 0, 0, 1, if(showwaypoints) getwaypoints());
 
+    VAR(IDF_PERSIST, autosavewaypoints, 0, 1, 1);
     VAR(IDF_PERSIST, showwaypointsdrop, 0, 1, 1);
     VAR(IDF_PERSIST, showwaypointsradius, 0, 256, VAR_MAX);
     VAR(IDF_PERSIST|IDF_HEX, showwaypointscolour, 0, 0xFF00FF, 0xFFFFFF);
@@ -17,9 +18,14 @@ namespace ai
 
     bool dbgfocus(gameent *d)  { return d->ai && (!aidebugfocus || d == game::focus || (aidebugfocus != 2 && !game::focus->ai)); }
 
+    void savemap(bool force, const char *mname)
+    {
+        if(autosavewaypoints || force) savewaypoints(true, mname);
+    }
+
     void startmap(bool empty)    // called just after a map load
     {
-        savewaypoints();
+        if(autosavewaypoints) savewaypoints();
         clearwaypoints(true);
         showwaypoints = dropwaypoints = 0;
     }
diff --git a/src/game/ai.h b/src/game/ai.h
index 4b8e60e..a6e8e1c 100644
--- a/src/game/ai.h
+++ b/src/game/ai.h
@@ -310,6 +310,7 @@ namespace ai
     extern vec aitarget;
     extern int aidebug, showaiinfo;
 
+    extern void savemap(bool force = false, const char *mname = "");
     extern void startmap(bool empty);
 
     extern float viewdist(int x = 101);
diff --git a/src/game/auth.h b/src/game/auth.h
index f7aa701..c367fbe 100644
--- a/src/game/auth.h
+++ b/src/game/auth.h
@@ -395,6 +395,7 @@ namespace auth
                 ci->totalpoints = ci->localtotalpoints + atoi(w[2]);
                 ci->totalfrags = ci->localtotalfrags + atoi(w[3]);
                 ci->totaldeaths = ci->localtotaldeaths + atoi(w[4]);
+                sendf(-1, 1, "ri5", N_TOTALS, ci->clientnum, ci->totalpoints, ci->totalfrags, ci->totaldeaths);
             }
         }
         else if(!strcmp(w[0], "failserverauth")) serverauthfailed();
diff --git a/src/game/client.cpp b/src/game/client.cpp
index 3ab9c0a..5a236d1 100644
--- a/src/game/client.cpp
+++ b/src/game/client.cpp
@@ -2,12 +2,9 @@
 
 namespace client
 {
-    bool sendplayerinfo = false, sendgameinfo = false, sendcrcinfo = false, isready = false, remote = false,
-        demoplayback = false, needsmap = false, gettingmap = false;
-    int lastping = 0, sessionid = 0, sessionver = 0, lastplayerinfo = 0, mastermode = 0;
+    bool sendplayerinfo = false, sendgameinfo = false, sendcrcinfo = false, isready = false, remote = false, demoplayback = false;
+    int needsmap = 0, gettingmap = 0, lastping = 0, sessionid = 0, sessionver = 0, lastplayerinfo = 0, mastermode = 0, needclipboard = -1, demonameid = 0;
     string connectpass = "";
-    int needclipboard = -1;
-    int demonameid = 0;
     hashtable<int, const char *>demonames;
 
     VAR(0, debugmessages, 0, 0, 1);
@@ -1031,8 +1028,8 @@ namespace client
     void gamedisconnect(int clean)
     {
         if(editmode) toggleedit();
-        gettingmap = needsmap = remote = isready = sendplayerinfo = sendgameinfo = sendcrcinfo = false;
-        sessionid = sessionver = lastplayerinfo = mastermode = 0;
+        remote = isready = sendplayerinfo = sendgameinfo = sendcrcinfo = false;
+        gettingmap = needsmap = sessionid = sessionver = lastplayerinfo = mastermode = 0;
         messages.shrink(0);
         mapvotes.shrink(0);
         messagereliable = false;
@@ -1295,20 +1292,20 @@ namespace client
         if(m_capture(game::gamemode)) capture::reset();
         else if(m_defend(game::gamemode)) defend::reset();
         else if(m_bomber(game::gamemode)) bomber::reset();
-        needsmap = gettingmap = false;
+        needsmap = gettingmap = 0;
         smartmusic(true);
         if(crc < -1 || !name || !*name || !load_world(name, crc)) switch(crc)
         {
             case -1:
                 if(!mapcrc) emptymap(0, true, name);
-                needsmap = gettingmap = false;
+                needsmap = gettingmap = 0;
                 sendcrcinfo = true; // the server wants us to start
                 break;
             case -2:
                 conoutf("waiting for server to request the map..");
             default:
                 emptymap(0, true, name);
-                needsmap = true;
+                needsmap = totalmillis;
                 break;
         }
         else sendcrcinfo = true;
@@ -1354,6 +1351,7 @@ namespace client
             {
                 int filetype = getint(p), filecrc = getint(p);
                 string fname;
+                gettingmap = totalmillis;
                 getstring(fname, p);
                 data += p.length();
                 len -= p.length();
@@ -1367,7 +1365,6 @@ namespace client
                     conoutft(CON_EVENT, "\frfailed to open map file: \fc%s", ffext);
                     break;
                 }
-                gettingmap = true;
                 f->write(data, len);
                 delete f;
                 conoutft(CON_EVENT, "\fywrote map file: \fc%s (%d %s) [0x%.8x]", ffext, len, len != 1 ? "bytes" : "byte", filecrc);
@@ -1656,6 +1653,8 @@ namespace client
         sendstring(game::player1->vanity, p);
         putint(p, game::player1->loadweap.length());
         loopv(game::player1->loadweap) putint(p, game::player1->loadweap[i]);
+        putint(p, game::player1->randweap.length());
+        loopv(game::player1->randweap) putint(p, game::player1->randweap[i]);
 
         string hash = "";
         if(connectpass[0])
@@ -1832,6 +1831,12 @@ namespace client
                 else if(m_bomber(game::gamemode)) bomber::sendaffinity(p);
                 sendgameinfo = false;
             }
+            if(gs_playing(game::gamestate) && needsmap && !gettingmap && totalmillis-needsmap >= 30000)
+            {
+                p.reliable();
+                putint(p, N_GETMAP);
+                needsmap = totalmillis;
+            }
         }
         if(messages.length())
         {
@@ -2465,6 +2470,17 @@ namespace client
                     break;
                 }
 
+                case N_TOTALS:
+                {
+                    int acn = getint(p), totalp = getint(p), totalf = getint(p), totald = getint(p);
+                    gameent *v = game::getclient(acn);
+                    if(!v) break;
+                    v->totalpoints = totalp;
+                    v->totalfrags = totalf;
+                    v->totaldeaths = totald;
+                    break;
+                }
+
                 case N_DROP:
                 {
                     int trg = getint(p), weap = getint(p), ds = getint(p);
@@ -2477,8 +2493,8 @@ namespace client
                     }
                     if(isweap(weap) && m)
                     {
-                        m->weapswitch(weap, lastmillis, weaponswitchdelay);
-                        playsound(WSND(weap, S_W_SWITCH), m->o, m, 0, -1, -1, -1, &m->wschan);
+                        if(m->weapswitch(weap, lastmillis, weaponswitchdelay))
+                            playsound(WSND(weap, S_W_SWITCH), m->o, m, 0, -1, -1, -1, &m->wschan);
                     }
                     break;
                 }
@@ -3114,13 +3130,13 @@ namespace client
                 case N_SENDMAP:
                 {
                     conoutf("\fymap data has been uploaded to the server");
-                    //if(needsmap && !gettingmap) addmsg(N_GETMAP, "r");
                     break;
                 }
 
                 case N_FAILMAP:
                 {
-                    needsmap = gettingmap = false;
+                    conoutf("\fyfailed to get a valid map");
+                    needsmap = gettingmap = 0;
                     break;
                 }
 
@@ -3131,7 +3147,7 @@ namespace client
                     if(size >= 0) emptymap(size, true, text);
                     else enlargemap(size == -2, true);
                     mapvotes.shrink(0);
-                    needsmap = false;
+                    needsmap = 0;
                     if(d)
                     {
                         int newsize = 0;
diff --git a/src/game/entities.cpp b/src/game/entities.cpp
index 78c84a3..4384a72 100644
--- a/src/game/entities.cpp
+++ b/src/game/entities.cpp
@@ -405,7 +405,7 @@ namespace entities
         {
             d->setweapstate(weap, W_S_SWITCH, weaponswitchdelay, lastmillis);
             d->ammo[weap] = -1;
-            if(d->weapselect != weap)
+            if(d->weapselect != weap && weap < W_ALL)
             {
                 d->addlastweap(d->weapselect);
                 d->weapselect = weap;
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 4e75117..00976a0 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -4,7 +4,7 @@ namespace game
 {
     int nextmode = G_EDITMODE, nextmuts = 0, gamestate = G_S_WAITING, gamemode = G_EDITMODE, mutators = 0, maptime = 0, timeremaining = 0, lasttimeremain = 0,
         lastcamera = 0, lasttvcam = 0, lasttvchg = 0, lastzoom = 0, spectvfollowing = -1, starttvcamdyn = -1, lastcamcn = -1;
-    bool prevzoom = false, zooming = false, inputmouse = false, inputview = false, inputmode = false;
+    bool zooming = false, inputmouse = false, inputview = false, inputmode = false;
     float swayfade = 0, swayspeed = 0, swaydist = 0, bobfade = 0, bobdist = 0;
     vec swaydir(0, 0, 0), swaypush(0, 0, 0);
 
@@ -232,7 +232,7 @@ namespace game
     FVAR(IDF_PERSIST, aboveheadsmooth, 0, 0.25f, 1);
     VAR(IDF_PERSIST, aboveheadsmoothmillis, 1, 100, 10000);
 
-    VAR(IDF_PERSIST, eventiconfade, 500, 7000, VAR_MAX);
+    VAR(IDF_PERSIST, eventiconfade, 500, 8000, VAR_MAX);
     VAR(IDF_PERSIST, eventiconshort, 500, 3500, VAR_MAX);
 
     VAR(IDF_PERSIST, showobituaries, 0, 4, 5); // 0 = off, 1 = only me, 2 = 1 + announcements, 3 = 2 + but dying bots, 4 = 3 + but bot vs bot, 5 = all
@@ -251,7 +251,7 @@ namespace game
     VAR(IDF_PERSIST, playreloadnotify, 0, 3, 15);
     VAR(IDF_PERSIST, reloadnotifyvol, -1, -1, 255);
 
-    VAR(IDF_PERSIST, deathanim, 0, 3, 3); // 0 = hide player when dead, 1 = old death animation, 2 = ragdolls, 3 = ragdolls, but hide in duke
+    VAR(IDF_PERSIST, deathanim, 0, 2, 3); // 0 = hide player when dead, 1 = old death animation, 2 = ragdolls, 3 = ragdolls, but hide in duke
     VAR(IDF_PERSIST, deathfade, 0, 1, 1); // 0 = don't fade out dead players, 1 = fade them out
     VAR(IDF_PERSIST, deathscale, 0, 0, 1); // 0 = don't scale out dead players, 1 = scale them out
     VAR(IDF_PERSIST, deathmaxfade, 0, 0, VAR_MAX);
@@ -514,8 +514,7 @@ namespace game
     {
         if(on != zooming)
         {
-            lastzoom = millis;
-            prevzoom = zooming;
+            lastzoom = millis - max(W(focus->weapselect, cookzoom) - (millis - lastzoom), 0);
             if(zoomdefault >= 0 && on) zoomlevel = zoomdefault;
         }
         checkzoom();
@@ -530,13 +529,6 @@ namespace game
     }
     ICOMMAND(0, iszooming, "", (), intret(inzoom() ? 1 : 0));
 
-    bool inzoomswitch()
-    {
-        if(lastzoom && ((zooming && lastmillis-lastzoom >= W(focus->weapselect, cookzoom)/2) || (!zooming && lastmillis-lastzoom <= W(focus->weapselect, cookzoom)/2)))
-            return true;
-        return false;
-    }
-
     void resetsway()
     {
         swaydir = swaypush = vec(0, 0, 0);
@@ -1869,6 +1861,11 @@ namespace game
     {
     }
 
+    void savemap(bool force, const char *mname)
+    {
+        ai::savemap(force, mname);
+    }
+
     void startmap(bool empty) // called just after a map load
     {
         ai::startmap(empty);
diff --git a/src/game/game.h b/src/game/game.h
index 51797ac..87d546d 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -4,7 +4,7 @@
 #include "engine.h"
 
 #define VERSION_GAMEID "fps"
-#define VERSION_GAME 229
+#define VERSION_GAME 230
 #define VERSION_DEMOMAGIC "RED_ECLIPSE_DEMO"
 
 #define MAXAI 256
@@ -292,7 +292,7 @@ extern const char *sendmaptypes[SENDMAP_MAX];
 enum
 {
     N_CONNECT = 0, N_SERVERINIT, N_WELCOME, N_CLIENTINIT, N_POS, N_SPHY, N_TEXT, N_COMMAND, N_ANNOUNCE, N_DISCONNECT,
-    N_SHOOT, N_DESTROY, N_STICKY, N_SUICIDE, N_DIED, N_POINTS, N_DAMAGE, N_SHOTFX,
+    N_SHOOT, N_DESTROY, N_STICKY, N_SUICIDE, N_DIED, N_POINTS, N_TOTALS, N_DAMAGE, N_SHOTFX,
     N_LOADW, N_TRYSPAWN, N_SPAWNSTATE, N_SPAWN, N_DROP, N_WSELECT,
     N_MAPCHANGE, N_MAPVOTE, N_CLEARVOTE, N_CHECKPOINT, N_ITEMSPAWN, N_ITEMUSE, N_TRIGGER, N_EXECLINK,
     N_PING, N_PONG, N_CLIENTPING, N_TICK, N_ITEMACC, N_SERVMSG, N_GETGAMEINFO, N_GAMEINFO, N_RESUME,
@@ -314,7 +314,7 @@ char msgsizelookup(int msg)
     static const int msgsizes[] =               // size inclusive message token, 0 for variable or not-checked sizes
     {
         N_CONNECT, 0, N_SERVERINIT, 5, N_WELCOME, 2, N_CLIENTINIT, 0, N_POS, 0, N_SPHY, 0, N_TEXT, 0, N_COMMAND, 0, N_ANNOUNCE, 0, N_DISCONNECT, 3,
-        N_SHOOT, 0, N_DESTROY, 0, N_STICKY, 0, N_SUICIDE, 4, N_DIED, 0, N_POINTS, 4, N_DAMAGE, 14, N_SHOTFX, 0,
+        N_SHOOT, 0, N_DESTROY, 0, N_STICKY, 0, N_SUICIDE, 4, N_DIED, 0, N_POINTS, 4, N_TOTALS, 0, N_DAMAGE, 14, N_SHOTFX, 0,
         N_LOADW, 0, N_TRYSPAWN, 2, N_SPAWNSTATE, 0, N_SPAWN, 0, N_DROP, 0, N_WSELECT, 0,
         N_MAPCHANGE, 0, N_MAPVOTE, 0, N_CLEARVOTE, 0, N_CHECKPOINT, 0, N_ITEMSPAWN, 3, N_ITEMUSE, 0, N_TRIGGER, 0, N_EXECLINK, 3,
         N_PING, 2, N_PONG, 2, N_CLIENTPING, 2, N_TICK, 3, N_ITEMACC, 0, N_SERVMSG, 0, N_GETGAMEINFO, 0, N_GAMEINFO, 0, N_RESUME, 0,
@@ -555,7 +555,7 @@ struct clientstate
         actortype(A_PLAYER), spawnpoint(-1), ownernum(-1), skill(0), points(0), frags(0), deaths(0), totalpoints(0), totalfrags(0), totaldeaths(0), spree(0), lasttimeplayed(0), timeplayed(0),
         cpmillis(0), cptime(0), queuepos(-1), quarantine(false)
     {
-        setvanity();
+        vanity[0] = '\0';
         loadweap.shrink(0);
         lastweap.shrink(0);
         randweap.shrink(0);
@@ -563,10 +563,10 @@ struct clientstate
     }
     ~clientstate() {}
 
-    bool setvanity(const char *v = "")
+    bool setvanity(const char *v)
     {
         bool changed = strcmp(v, vanity);
-        copystring(vanity, v);
+        if(changed) copystring(vanity, v);
         return changed;
     }
 
@@ -675,9 +675,9 @@ struct clientstate
         weaptime[weap] = millis-offtime;
     }
 
-    void weapswitch(int weap, int millis, int delay = 0, int state = W_S_SWITCH)
+    bool weapswitch(int weap, int millis, int delay = 0, int state = W_S_SWITCH)
     {
-        if(isweap(weap))
+        if(isweap(weap) && weap < W_ALL)
         {
             if(isweap(weapselect))
             {
@@ -686,7 +686,9 @@ struct clientstate
             }
             weapselect = weap;
             setweapstate(weap, state, delay, millis);
+            return true;
         }
+        return false;
     }
 
     bool weapwaited(int weap, int millis, int skip = 0)
@@ -815,6 +817,7 @@ struct clientstate
         health = heal > 0 ? heal : (m_insta(gamemode, mutators) ? 1 : m_health(gamemode, mutators, actortype));
         int s = sweap;
         if(!isweap(s)) s = m_weapon(actortype, gamemode, mutators);
+        if(s >= W_ALL) s = W_CLAW;
         if(isweap(s))
         {
             ammo[s] = max(1, W(s, ammomax));
@@ -1562,8 +1565,8 @@ struct cament
 
 namespace client
 {
-    extern int showpresence, showteamchange;
-    extern bool demoplayback, isready, needsmap, gettingmap;
+    extern int showpresence, showteamchange, needsmap, gettingmap;
+    extern bool demoplayback, isready;
     extern vector<uchar> messages;
     extern void clearvotes(gameent *d, bool msg = false);
     extern void ignore(int cn);
@@ -1721,7 +1724,6 @@ namespace game
     extern int deadzone();
     extern void checkzoom();
     extern bool inzoom();
-    extern bool inzoomswitch();
     extern void zoomview(bool down);
     extern bool tvmode(bool check = true, bool force = true);
     extern void resetcamera(bool cam = true, bool input = true);
diff --git a/src/game/hud.cpp b/src/game/hud.cpp
index 178faae..48eb5ef 100644
--- a/src/game/hud.cpp
+++ b/src/game/hud.cpp
@@ -28,6 +28,7 @@ namespace hud
     VAR(IDF_PERSIST, showconsole, 0, 2, 2);
     VAR(IDF_PERSIST, shownotices, 0, 3, 4);
     VAR(IDF_PERSIST, showevents, 0, 3, 4);
+    VAR(IDF_PERSIST, showeventicons, 0, 1, 7);
     VAR(IDF_PERSIST, showloadingaspect, 0, 2, 3);
     VAR(IDF_PERSIST, showloadingmapbg, 0, 1, 1);
     VAR(IDF_PERSIST, showloadinggpu, 0, 1, 1);
@@ -94,9 +95,10 @@ namespace hud
     FVAR(IDF_PERSIST, noticepadx, FVAR_MIN, 0, FVAR_MAX);
     FVAR(IDF_PERSIST, noticepady, FVAR_MIN, 0, FVAR_MAX);
     VAR(IDF_PERSIST, noticetitle, 0, 10000, 60000);
-    FVAR(IDF_PERSIST, eventoffset, -1, 0.61f, 1);
+    FVAR(IDF_PERSIST, eventoffset, -1, 0.58f, 1);
     FVAR(IDF_PERSIST, eventblend, 0, 1, 1);
     FVAR(IDF_PERSIST, eventscale, 1e-4f, 1, 1000);
+    FVAR(IDF_PERSIST, eventiconscale, 1e-4f, 2.5f, 1000);
     FVAR(IDF_PERSIST, eventpadx, FVAR_MIN, 0.5f, FVAR_MAX);
     FVAR(IDF_PERSIST, eventpady, FVAR_MIN, 0.125f, FVAR_MAX);
     VAR(IDF_PERSIST, noticetime, 0, 5000, VAR_MAX);
@@ -1645,7 +1647,7 @@ namespace hud
                 pushfont("little");
                 if(!client::demoplayback)
                 {
-                    ty += draw_textf("Press \fs\fw\f{=1:spectator 0}\fS to join the game", tx, ty, int(FONTW*noticepadx), int(FONTH*noticepady), tr, tg, tb, tf, TEXT_CENTERED, -1, tw, 1);
+                    ty += draw_textf("Press \fs\fw\f{=1:spectate 0}\fS to join the game", tx, ty, int(FONTW*noticepadx), int(FONTH*noticepady), tr, tg, tb, tf, TEXT_CENTERED, -1, tw, 1);
                     if(m_play(game::gamemode) && m_team(game::gamemode, game::mutators) && shownotices >= 2)
                         ty += draw_textf("Press \fs\fw\f{=1:showgui team}\fS to join a team", tx, ty, int(FONTW*noticepadx), int(FONTH*noticepady), tr, tg, tb, tf, TEXT_CENTERED, -1, tw, 1);
                 }
@@ -1676,7 +1678,7 @@ namespace hud
             tf = int(hudblend*eventblend*255), tr = 255, tg = 255, tb = 255,
             tw = int((hudwidth-((hudsize*edgesize)*2+(hudsize*inventoryleft)+(hudsize*inventoryright)))/eventscale);
         if(eventtone) skewcolour(tr, tg, tb, eventtone);
-        pushfont("huge");
+        pushfont("emphasis");
         if(!gs_playing(game::gamestate))
             ty -= draw_textf("%s", tx, ty, int(FONTW*noticepadx), int(FONTH*noticepady), 255, 255, 255, tf, TEXT_CENTERED, -1, tw, 1, gamestates[2][game::gamestate])+FONTH/3;
         else
@@ -1694,7 +1696,33 @@ namespace hud
         if(m_capture(game::gamemode)) capture::drawevents(hudwidth, hudheight, tx, ty, tr, tg, tb, tf/255.f);
         else if(m_defend(game::gamemode)) defend::drawevents(hudwidth, hudheight, tx, ty, tr, tg, tb, tf/255.f);
         else if(m_bomber(game::gamemode)) bomber::drawevents(hudwidth, hudheight, tx, ty, tr, tg, tb, tf/255.f);
-        popfont();
+        if(showeventicons && game::focus->state != CS_EDITING && game::focus->state != CS_SPECTATOR) loopv(game::focus->icons)
+        {
+            if(game::focus->icons[i].type == eventicon::AFFINITY && !(showeventicons&2)) continue;
+            if(game::focus->icons[i].type == eventicon::WEAPON && !(showeventicons&4)) continue;
+            int millis = lastmillis-game::focus->icons[i].millis;
+            if(millis <= game::focus->icons[i].fade)
+            {
+                Texture *t = textureload(icontex(game::focus->icons[i].type, game::focus->icons[i].value));
+                if(t && t != notexture)
+                {
+                    int olen = min(game::focus->icons[i].length/5, 1000), ilen = olen/2, colour = 0xFFFFFF;
+                    float skew = millis < ilen ? millis/float(ilen) : (millis > game::focus->icons[i].fade-olen ? (game::focus->icons[i].fade-millis)/float(olen) : 1.f),
+                          fade = blend*eventblend*skew;
+                    int size = int(FONTH*skew*eventiconscale), width = int((t->w/float(t->h))*size), rsize = game::focus->icons[i].type < eventicon::SORTED ? int(size*2/3) : int(size);
+                    switch(game::focus->icons[i].type)
+                    {
+                        case eventicon::WEAPON: colour = W(game::focus->icons[i].value, colour); break;
+                        case eventicon::AFFINITY: colour = m_bomber(game::gamemode) ? pulsecols[PULSE_DISCO][clamp((totalmillis/100)%PULSECOLOURS, 0, PULSECOLOURS-1)] : TEAM(game::focus->icons[i].value, colour); break;
+                        default: break;
+                    }
+                    glBindTexture(GL_TEXTURE_2D, t->id);
+                    gle::color(vec::hexcolor(colour), fade);
+                    drawtexture(tx-width/2, ty-rsize/2, width, size);
+                    ty -= rsize;
+                }
+            }
+        }
         pophudmatrix();
     }
 
@@ -3368,8 +3396,8 @@ namespace hud
         }
         y = h-bottom-FONTH/2;
         if(showloadinggpu) y -= draw_textf("%s (%s v%s)", w-FONTH, y, 0, 0, 255, 255, 255, 255, TEXT_RIGHT_UP, -1, -1, 1, gfxrenderer, gfxvendor, gfxversion);
-        if(showloadingversion) y -= draw_textf("%s v%s-%s%d-%s (%s)", w-FONTH, y, 0, 0, 255, 255, 255, 255, TEXT_RIGHT_UP, -1, -1, 1, VERSION_NAME, VERSION_STRING, versionplatname, versionarch, versionbranch, VERSION_RELEASE);
-        if(showloadingurl) y -= draw_textf("%s", w-FONTH, y, 0, 0, 255, 255, 255, 255, TEXT_RIGHT_UP, -1, -1, 1, VERSION_URL);
+        if(showloadingversion) y -= draw_textf("%s v%s-%s%d-%s (%s)", w-FONTH, y, 0, 0, 255, 255, 255, 255, TEXT_RIGHT_UP, -1, -1, 1, versionname, versionstring, versionplatname, versionarch, versionbranch, versionrelease);
+        if(showloadingurl && *versionurl) y -= draw_textf("%s", w-FONTH, y, 0, 0, 255, 255, 255, 255, TEXT_RIGHT_UP, -1, -1, 1, versionurl);
         popfont();
     }
 
diff --git a/src/game/physics.cpp b/src/game/physics.cpp
index 62ab8ae..52439f4 100644
--- a/src/game/physics.cpp
+++ b/src/game/physics.cpp
@@ -1411,7 +1411,7 @@ namespace physics
             return insideworld(d->o);
         }
         vec orig = d->o;
-        float maxrad = max(d->radius, max(d->xradius, d->yradius));
+        float maxrad = max(d->radius, max(d->xradius, d->yradius))+1;
         #define doposchk \
             if(insideworld(d->o) && !collide(d, vec(0, 0, 0), 0, avoidplayers)) \
             { \
@@ -1427,32 +1427,18 @@ namespace physics
                 doposchk; \
             }
         doposchk;
-        inmapchk(20, d->o.z += (d->height+d->aboveeye)*n/10.f);
-        if(gameent::is(d))
+        if(gameent::is(d)) loopk(18)
         {
-            vec dir = vec(d->yaw, d->pitch).mul(maxrad);
-            if(!dir.iszero())
-            {
-                inmapchk(200, d->o.add(vec(dir).mul(n/10.f)));
-                inmapchk(50, d->o.add(vec(dir).mul(-n/10.f)));
-            }
-            dir = vec(d->vel).normalize().mul(maxrad);
-            if(!dir.iszero())
-            {
-                inmapchk(200, d->o.add(vec(dir).mul(n/10.f)));
-                inmapchk(50, d->o.add(vec(dir).mul(-n/10.f)));
-            }
-            inmapchk(50, d->o.add(vec((rnd(21)-10)/10.f, (rnd(21)-10)/10.f, (rnd(21)-10)/10.f).normalize().mul(maxrad).mul(vec(n/10.f, n/10.f, n/25.f))));
+            vec dir = vec(d->yaw*RAD, d->pitch*RAD).rotate_around_z(k*20.f*RAD);
+            if(!dir.iszero()) inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad)));
         }
-        else
+        if(!d->vel.iszero()) loopk(18)
         {
-            vec dir = vec(d->vel).normalize().mul(maxrad);
-            if(!dir.iszero())
-            {
-                inmapchk(100, d->o.add(vec(dir).mul(n/10.f)));
-                inmapchk(20, d->o.add(vec(dir).mul(-n/10.f)));
-            }
+            vec dir = vec(d->vel).normalize().rotate_around_z(k*20.f*RAD);
+            inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad)));
         }
+        inmapchk(100, d->o.add(vec((rnd(21)-10)/10.f, (rnd(21)-10)/10.f, (rnd(21)-10)/10.f).normalize().mul(vec(n/10.f+maxrad, n/10.f+maxrad, n/25.f+maxrad))));
+        inmapchk(20, d->o.z += (d->height+d->aboveeye)*n/10.f);
         d->o = orig;
         d->resetinterp();
         return false;
diff --git a/src/game/projs.cpp b/src/game/projs.cpp
index f487168..f5a12aa 100644
--- a/src/game/projs.cpp
+++ b/src/game/projs.cpp
@@ -964,7 +964,7 @@ namespace projs
             case PRJ_AFFINITY:
             {
                 proj.height = proj.aboveeye = proj.radius = proj.xradius = proj.yradius = 4;
-                vec dir = vec(proj.to).sub(proj.from).normalize();
+                vec dir = vec(proj.to).sub(proj.from).safenormalize();
                 vectoyawpitch(dir, proj.yaw, proj.pitch);
                 proj.reflectivity = 0.f;
                 proj.escaped = true;
@@ -1523,7 +1523,7 @@ namespace projs
                         {
                             if(type != W_ZAPPER || !proj.owner || proj.owner->weapselect != proj.weap || WK(proj.flags))
                                 proj.to = vec(proj.o).sub(vec(proj.vel).normalize().mul(size));
-                            else proj.to = vec(proj.o).sub(vec(proj.o).sub(from).normalize().mul(size));
+                            else proj.to = vec(proj.o).sub(vec(proj.o).sub(from).safenormalize().mul(size));
                             part_flare(proj.to, proj.o, 1, type != W_ZAPPER ? PART_FLARE : PART_LIGHTZAP_FLARE, FWCOL(H, partcol, proj), WF(WK(proj.flags), proj.weap, partsize, WS(proj.flags))*proj.curscale, 0.85f*trans);
                             if(projhints) part_flare(proj.to, proj.o, 1, type != W_ZAPPER ? PART_FLARE : PART_LIGHTZAP_FLARE, projhint(proj.owner, FWCOL(H, partcol, proj)), WF(WK(proj.flags), proj.weap, partsize, WS(proj.flags))*projhintsize*proj.curscale, projhintblend*trans);
                             if(type != W_ZAPPER && !WK(proj.flags) && W2(proj.weap, fragweap, WS(proj.flags)) >= 0)
@@ -1708,7 +1708,7 @@ namespace projs
                         {
                             if(type != W_ZAPPER || !proj.owner || proj.owner->weapselect != proj.weap || WK(proj.flags))
                                 proj.to = vec(proj.o).sub(vec(proj.vel).normalize().mul(size));
-                            else proj.to = vec(proj.o).sub(vec(proj.o).sub(from).normalize().mul(size));
+                            else proj.to = vec(proj.o).sub(vec(proj.o).sub(from).safenormalize().mul(size));
                         }
                         if(expl > 0)
                         {
@@ -2084,7 +2084,7 @@ namespace projs
         float secs = float(qtime)/1000.f;
         if(proj.projtype == PRJ_AFFINITY && m_bomber(game::gamemode) && proj.target && !proj.lastbounce)
         {
-            vec targ = vec(proj.target->o).sub(proj.o).normalize();
+            vec targ = vec(proj.target->o).sub(proj.o).safenormalize();
             if(!targ.iszero())
             {
                 vec dir = vec(proj.vel).normalize();
@@ -2145,7 +2145,7 @@ namespace projs
             {
                 float amt = clamp(WF(WK(proj.flags), proj.weap, speeddelta, WS(proj.flags))*secs, 1e-8f, 1.f),
                       mag = max(proj.vel.magnitude(), physics::movevelocity(&proj));
-                dir.mul(1.f-amt).add(vec(proj.dest).sub(proj.o).normalize().mul(amt)).normalize();
+                dir.mul(1.f-amt).add(vec(proj.dest).sub(proj.o).safenormalize().mul(amt)).normalize();
                 if(!dir.iszero()) (proj.vel = dir).mul(mag);
             }
         }
@@ -2200,7 +2200,7 @@ namespace projs
         float dist = tracecollide(&proj, proj.from, ray, maxdist, RAY_CLIPMAT|RAY_ALPHAPOLY, proj.projcollide&COLLIDE_DYNENT);
         if(dist >= 0)
         {
-            vec dir = vec(proj.to).sub(proj.from).normalize();
+            vec dir = vec(proj.to).sub(proj.from).safenormalize();
             proj.o = vec(proj.from).add(vec(dir).mul(dist));
             switch(impact(proj, dir, hitplayer, hitflags, hitsurface))
             {
diff --git a/src/game/scoreboard.cpp b/src/game/scoreboard.cpp
index edfa41c..3af6220 100644
--- a/src/game/scoreboard.cpp
+++ b/src/game/scoreboard.cpp
@@ -468,10 +468,10 @@ namespace hud
                             scoregroup &sg = k == numgroups ? spectators : *groups[k];
                             loopscoregroup({
                                 if(scorebotinfo && o->actortype > A_PLAYER) hasbots = true;
-                                namepad = max(namepad, (float)text_width(game::colourname(o, NULL, false, true))/FONTW*0.51f);
+                                namepad = max(namepad, text_widthf(game::colourname(o, NULL, false, true))/FONTW*0.51f);
                                 if(scorehandles && o->handle[0])
                                 {
-                                    handlepad = max(handlepad, (float)text_width(o->handle)/FONTW*0.51f);
+                                    handlepad = max(handlepad, text_widthf(o->handle)/FONTW*0.51f);
                                     hashandle = true;
                                 }
                                 if(scoreipinfo)
@@ -479,7 +479,7 @@ namespace hud
                                     const char *host = scorehost(o, false);
                                     if(host && *host)
                                     {
-                                        ippad = max(ippad, (float)text_width(host)/FONTW*0.51f);
+                                        ippad = max(ippad, text_widthf(host)/FONTW*0.51f);
                                         if(o->ownernum != game::player1->clientnum) hasip = true;
                                     }
                                 }
@@ -488,7 +488,7 @@ namespace hud
                                     const char *host = scorehost(o, true);
                                     if(host && *host)
                                     {
-                                        hostpad = max(hostpad, (float)text_width(host)/FONTW*0.51f);
+                                        hostpad = max(hostpad, text_widthf(host)/FONTW*0.51f);
                                         if(o->ownernum != game::player1->clientnum) hashost = true;
                                     }
                                 }
@@ -497,7 +497,7 @@ namespace hud
                                     const char *ver = scoreversion(o);
                                     if(ver && *ver)
                                     {
-                                        verpad = max(verpad, (float)text_width(ver)/FONTW*0.51f);
+                                        verpad = max(verpad, text_widthf(ver)/FONTW*0.51f);
                                         if(o->ownernum != game::player1->clientnum) hasver = true;
                                     }
                                 }
diff --git a/src/game/server.cpp b/src/game/server.cpp
index 341497d..63d3de2 100644
--- a/src/game/server.cpp
+++ b/src/game/server.cpp
@@ -261,7 +261,7 @@ namespace server
 
     struct servstate : baseent, clientstate
     {
-        int rewards[2], shotdamage, damage, lasttimewielded, lasttimeloadout[W_ALL], aireinit,
+        int rewards[2], shotdamage, damage, lasttimewielded, lasttimeloadout[W_MAX], aireinit,
             lastresowner[WR_MAX], lasttimealive, timealive, lasttimeactive, timeactive, lastresweapon[WR_MAX], lasthurt,
             localtotalpoints, localtotalfrags, localtotaldeaths;
         bool lastresalt[W_MAX];
@@ -277,10 +277,10 @@ namespace server
 
         int warnings[WARN_MAX][2];
 
-        servstate() : lasttimewielded(0), aireinit(0), lasttimealive(0), timealive(0), timeactive(0), lasthurt(0), localtotalpoints(0), localtotalfrags(0), localtotaldeaths(0)
+        servstate() : lasttimewielded(0), aireinit(0), lasttimealive(0), timealive(0), lasttimeactive(0), timeactive(0), lasthurt(0), localtotalpoints(0), localtotalfrags(0), localtotaldeaths(0)
         {
             loopi(WARN_MAX) loopj(2) warnings[i][j] = 0;
-            loopi(W_ALL) lasttimeloadout[i] = 0;
+            loopi(W_MAX) lasttimeloadout[i] = 0;
             resetresidualowner();
         }
 
@@ -330,7 +330,7 @@ namespace server
                 int millis = totalmillis-lasttimewielded, secs = millis/1000;
                 weapstats[weapselect].timewielded += secs;
                 lasttimewielded = totalmillis+(secs*1000)-millis;
-                loopi(W_ALL)
+                loopi(W_MAX)
                 {
                     if(lasttimeloadout[i] && holdweap(i, m_weapon(actortype, gamemode, mutators), lastmillis))
                     {
@@ -344,7 +344,7 @@ namespace server
             else
             {
                 lasttimewielded = totalmillis ? totalmillis : 1;
-                loopi(W_ALL) lasttimeloadout[i] = totalmillis ? totalmillis : 1;
+                loopi(W_MAX) lasttimeloadout[i] = totalmillis ? totalmillis : 1;
             }
         }
 
@@ -468,12 +468,12 @@ namespace server
         string name, handle;
         int points, frags, deaths, localtotalpoints, localtotalfrags, localtotaldeaths, spree, rewards, timeplayed, timealive, timeactive, shotdamage, damage, cptime, actortype;
         int warnings[WARN_MAX][2];
-        vector<teamkill> teamkills;
+        bool quarantine;
         weaponstats weapstats[W_MAX];
+        vector<teamkill> teamkills;
         vector<capturestats> captures;
         vector<bombstats> bombings;
         vector<ffaroundstats> ffarounds;
-        bool quarantine;
 
         void save(clientinfo *ci)
         {
@@ -488,17 +488,21 @@ namespace server
             timeplayed = ci->timeplayed;
             timealive = ci->timealive;
             timeactive = ci->timeactive;
-            teamkills = ci->teamkills;
             shotdamage = ci->shotdamage;
             damage = ci->damage;
             cptime = ci->cptime;
             actortype = ci->actortype;
             loopi(W_MAX) weapstats[i] = ci->weapstats[i];
-            captures = ci->captures;
-            bombings = ci->bombings;
-            ffarounds = ci->ffarounds;
             loopi(WARN_MAX) loopj(2) warnings[i][j] = ci->warnings[i][j];
             quarantine = ci->quarantine;
+            teamkills.shrink(0);
+            loopv(ci->teamkills) teamkills.add(ci->teamkills[i]);
+            captures.shrink(0);
+            loopv(ci->captures) captures.add(ci->captures[i]);
+            bombings.shrink(0);
+            loopv(ci->bombings) bombings.add(ci->bombings[i]);
+            ffarounds.shrink(0);
+            loopv(ci->ffarounds) ffarounds.add(ci->ffarounds[i]);
         }
 
         void restore(clientinfo *ci)
@@ -517,16 +521,20 @@ namespace server
             ci->timeplayed = timeplayed;
             ci->timealive = timealive;
             ci->timeactive = timeactive;
-            ci->teamkills = teamkills;
             ci->shotdamage = shotdamage;
             ci->damage = damage;
             ci->cptime = cptime;
             loopi(W_MAX) ci->weapstats[i] = weapstats[i];
-            ci->captures = captures;
-            ci->bombings = bombings;
-            ci->ffarounds = ffarounds;
             loopi(WARN_MAX) loopj(2) ci->warnings[i][j] = warnings[i][j];
             ci->quarantine = quarantine;
+            ci->teamkills.shrink(0);
+            loopv(teamkills) ci->teamkills.add(teamkills[i]);
+            ci->captures.shrink(0);
+            loopv(captures) ci->captures.add(captures[i]);
+            ci->bombings.shrink(0);
+            loopv(bombings) ci->bombings.add(bombings[i]);
+            ci->ffarounds.shrink(0);
+            loopv(ffarounds) ci->ffarounds.add(ffarounds[i]);
         }
 
         void mapchange()
@@ -2324,7 +2332,7 @@ namespace server
         {
             lilswap(&hdr.gamever, 4);
             if(hdr.gamever!=VERSION_GAME)
-                formatstring(msg, "\frdemo \fs\fc%s\fS requires %s version of %s", file, hdr.gamever<VERSION_GAME ? "an older" : "a newer", VERSION_NAME);
+                formatstring(msg, "\frdemo \fs\fc%s\fS requires %s version of %s", file, hdr.gamever<VERSION_GAME ? "an older" : "a newer", versionname);
         }
         if(msg[0])
         {
@@ -2414,7 +2422,7 @@ namespace server
         {
             lilswap(&d.hdr.gamever, 4);
             if(d.hdr.gamever!=VERSION_GAME)
-                formatstring(msg, "\frdemo \fs\fc%s\fS requires \fs\fc%s\fS version of %s", name, d.hdr.gamever<VERSION_GAME ? "an older" : "a newer", VERSION_NAME);
+                formatstring(msg, "\frdemo \fs\fc%s\fS requires \fs\fc%s\fS version of %s", name, d.hdr.gamever<VERSION_GAME ? "an older" : "a newer", versionname);
         }
         delete f;
         if(msg[0])
@@ -3103,7 +3111,7 @@ namespace server
             ci->lasttimealive = totalmillis ? totalmillis : 1;
             ci->lasttimeactive = totalmillis ? totalmillis : 1;
             ci->lasttimewielded = totalmillis ? totalmillis : 1;
-            loopi(W_ALL) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
+            loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
             ci->quarantine = false;
             waiting(ci, DROP_RESET);
             if(smode) smode->entergame(ci);
@@ -3140,13 +3148,10 @@ namespace server
     bool getmap(clientinfo *ci, bool force)
     {
         if(gs_intermission(gamestate)) return false; // pointless
-        if(m_edit(gamemode) && numclients() <= 1)
+        if(ci && !numclients(ci->clientnum))
         {
-            if(ci)
-            {
-                ci->wantsmap = false;
-                sendf(ci->clientnum, 1, "ri", N_FAILMAP);
-            }
+            ci->wantsmap = false;
+            sendf(ci->clientnum, 1, "ri", N_FAILMAP);
             return false;
         }
         if(ci)
@@ -3231,7 +3236,7 @@ namespace server
             }
             return true;
         }
-        if(ci) srvmsgft(ci->clientnum, CON_EVENT, "\fysorry, unable to get a valid map..");
+        if(ci) srvmsgft(ci->clientnum, CON_EVENT, "\fysorry, unable to get a map..");
         sendf(-1, 1, "ri", N_FAILMAP);
         return false;
     }
@@ -4146,25 +4151,34 @@ namespace server
                         {
                             statalt = m->lastresalt[WR_BURN];
                             statweap = m->lastresweapon[WR_BURN];
-                            if(statalt) v->weapstats[statweap].damage2 += realdamage;
-                            else v->weapstats[statweap].damage1 += realdamage;
+                            if(isweap(statweap))
+                            {
+                                if(statalt) v->weapstats[statweap].damage2 += realdamage;
+                                else v->weapstats[statweap].damage1 += realdamage;
+                            }
                         }
                         if(flags&HIT_BLEED)
                         {
                             statalt = m->lastresalt[WR_BLEED];
                             statweap = m->lastresweapon[WR_BLEED];
-                            if(statalt) v->weapstats[statweap].damage2 += realdamage;
-                            else v->weapstats[statweap].damage1 += realdamage;
+                            if(isweap(statweap))
+                            {
+                                if(statalt) v->weapstats[statweap].damage2 += realdamage;
+                                else v->weapstats[statweap].damage1 += realdamage;
+                            }
                         }
                         if(flags&HIT_SHOCK)
                         {
                             statalt = m->lastresalt[WR_SHOCK];
                             statweap = m->lastresweapon[WR_SHOCK];
-                            if(statalt) v->weapstats[statweap].damage2 += realdamage;
-                            else v->weapstats[statweap].damage1 += realdamage;
+                            if(isweap(statweap))
+                            {
+                                if(statalt) v->weapstats[statweap].damage2 += realdamage;
+                                else v->weapstats[statweap].damage1 += realdamage;
+                            }
                         }
                     }
-                    else
+                    else if(isweap(statweap))
                     {
                         if(statalt) v->weapstats[statweap].damage2 += realdamage;
                         else v->weapstats[statweap].damage1 += realdamage;
@@ -4192,7 +4206,7 @@ namespace server
                     m->lastresweapon[WR_SHOCK] = fromweap;
                     m->lastresalt[WR_SHOCK] = statalt;
                 }
-                if(isweap(weap) && m != v && (!m_team(gamemode, mutators) || m->team != v->team) && first)
+                if(isweap(statweap) && m != v && (!m_team(gamemode, mutators) || m->team != v->team) && first)
                 {
                     if(WK(flags))
                     {
@@ -4220,8 +4234,11 @@ namespace server
                 v->frags++;
                 v->totalfrags++;
                 v->localtotalfrags++;
-                if(statalt) v->weapstats[statweap].frags2++;
-                else v->weapstats[statweap].frags1++;
+                if(isweap(statweap))
+                {
+                    if(statalt) v->weapstats[statweap].frags2++;
+                    else v->weapstats[statweap].frags1++;
+                }
             }
             else fragvalue = -fragvalue;
             bool isai = m->actortype >= A_ENEMY, isteamkill = false;
@@ -5661,7 +5678,7 @@ namespace server
         ci->lasttimealive = totalmillis ? totalmillis : 1;
         ci->lasttimeactive = totalmillis ? totalmillis : 1;
         ci->lasttimewielded = totalmillis ? totalmillis : 1;
-        loopi(W_ALL) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
+        loopi(W_MAX) ci->lasttimeloadout[i] = totalmillis ? totalmillis : 1;
 
         if(ci->handle[0]) // kick old logins
         {
@@ -5726,6 +5743,13 @@ namespace server
                             if(k >= W_LOADOUT) getint(p);
                             else ci->loadweap.add(getint(p));
                         }
+                        int rw = getint(p);
+                        ci->randweap.shrink(0);
+                        loopk(rw)
+                        {
+                            if(k >= W_LOADOUT) getint(p);
+                            else ci->randweap.add(getint(p));
+                        }
 
                         string password = "", authname = "";
                         getstring(password, p);
@@ -6014,7 +6038,7 @@ namespace server
                         if(hasmapdata()) srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS (server: \fs\fc0x%.8x\fS)", colourname(ci), ci->clientcrc, smapcrc);
                         else srvoutf(4, "\fy%s has map crc: \fs\fc0x%.8x\fS", colourname(ci), ci->clientcrc);
                     }
-                    getmap(crclocked(ci, true) ? ci : NULL);
+                    if(crclocked(ci, true)) getmap(ci);
                     if(ci->isready()) aiman::poke();
                     break;
                 }
@@ -6045,7 +6069,7 @@ namespace server
                 {
                     int lcn = getint(p), id = getint(p), weap = getint(p);
                     clientinfo *cp = (clientinfo *)getinfo(lcn);
-                    if(!hasclient(cp, ci) || !isweap(weap)) break;
+                    if(!hasclient(cp, ci) || !isweap(weap) || weap >= W_ALL) break;
                     switchevent *ev = new switchevent;
                     ev->id = id;
                     ev->weap = weap;
@@ -6964,8 +6988,9 @@ namespace server
                 case N_EDITENT:
                 {
                     int n = getint(p), oldtype = NOTUSED, newtype = NOTUSED;
+                    ivec o(0, 0, 0);
                     bool tweaked = false, inrange = n < MAXENTS;
-                    loopk(3) getint(p);
+                    loopk(3) o[k] = getint(p);
                     if(p.overread()) break;
                     if(sents.inrange(n)) oldtype = sents[n].type;
                     else if(inrange) while(sents.length() <= n) sents.add();
@@ -6984,13 +7009,25 @@ namespace server
                     }
                     if(inrange)
                     {
-                        if(oldtype == PLAYERSTART || sents[n].type == PLAYERSTART) setupspawns(true);
                         hasgameinfo = true;
-                        QUEUE_MSG;
-                        if(tweaked && enttype[sents[n].type].usetype != EU_NONE)
+                        sents[n].o = vec(o).div(DMF);
+                        packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+                        putint(q, N_CLIENT);
+                        putint(q, ci->clientnum);
+                        putint(q, N_EDITENT);
+                        putint(q, n);
+                        putint(q, o.x);
+                        putint(q, o.y);
+                        putint(q, o.z);
+                        putint(q, sents[n].type);
+                        putint(q, sents[n].attrs.length());
+                        loopvk(sents[n].attrs) putint(q, sents[n].attrs[k]);
+                        sendpacket(-1, 1, q.finalize(), ci->clientnum);
+                        if(tweaked)
                         {
                             if(enttype[sents[n].type].usetype == EU_ITEM) setspawn(n, true, true, true);
-                            if(sents[n].type == TRIGGER) setuptriggers(true);
+                            if(oldtype == PLAYERSTART || sents[n].type == PLAYERSTART) setupspawns(true);
+                            if(oldtype == TRIGGER || sents[n].type == TRIGGER) setuptriggers(true);
                         }
                     }
                     break;
@@ -7057,7 +7094,7 @@ namespace server
                 case N_GETMAP:
                 {
                     ci->ready = true;
-                    if(!getmap(ci) && numclients() <= 1) sendf(ci->clientnum, 1, "ri", N_FAILMAP);
+                    getmap(ci);
                     break;
                 }
 
diff --git a/src/game/vars.h b/src/game/vars.h
index cfd3ae1..fc87d3e 100644
--- a/src/game/vars.h
+++ b/src/game/vars.h
@@ -399,7 +399,7 @@ GVAR(IDF_GAMEMOD, botbalanceduel, -1, 2, VAR_MAX); // -1 = always use numplayers
 GVAR(IDF_GAMEMOD, botbalancesurvivor, -1, 2, VAR_MAX); // -1 = always use numplayers, 0 = don't balance, 1 or more = fill only with this many
 GVAR(IDF_GAMEMOD, botskillmin, 1, 60, 101);
 GVAR(IDF_GAMEMOD, botskillmax, 1, 80, 101);
-GFVAR(IDF_GAMEMOD, botskillfrags, -100, 0, 100);
+GFVAR(IDF_GAMEMOD, botskillfrags, -100, -1, 100);
 GFVAR(IDF_GAMEMOD, botskilldeaths, -100, 1, 100);
 GVAR(IDF_GAMEMOD, botlimit, 0, 32, MAXAI);
 GVAR(IDF_GAMEMOD, botoffset, VAR_MIN, 0, VAR_MAX);
diff --git a/src/game/weapons.cpp b/src/game/weapons.cpp
index e92ff16..e6ecfd6 100644
--- a/src/game/weapons.cpp
+++ b/src/game/weapons.cpp
@@ -79,6 +79,8 @@ namespace weapons
     bool weapselect(gameent *d, int weap, int filter, bool local)
     {
         if(!gs_playing(game::gamestate)) return false;
+        bool newoff = false;
+        int oldweap = d->weapselect;
         if(local)
         {
             int interrupts = filter;
@@ -86,19 +88,26 @@ namespace weapons
             if(!d->canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, interrupts))
             {
                 if(!d->canswitch(weap, m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, filter)) return false;
-                else if(!isweap(d->weapselect) || d->weapload[d->weapselect] <= 0) return false;
-                else
+                else if(!isweap(oldweap) || d->weapload[oldweap] <= 0) return false;
+                else newoff = true;
+            }
+        }
+        if(d->weapswitch(weap, lastmillis, weaponswitchdelay))
+        {
+            if(local)
+            {
+                if(newoff)
                 {
-                    int offset = d->weapload[d->weapselect];
-                    d->ammo[d->weapselect] = max(d->ammo[d->weapselect]-offset, 0);
-                    d->weapload[d->weapselect] = -d->weapload[d->weapselect];
+                    int offset = d->weapload[oldweap];
+                    d->ammo[oldweap] = max(d->ammo[oldweap]-offset, 0);
+                    d->weapload[oldweap] = -d->weapload[oldweap];
                 }
+                client::addmsg(N_WSELECT, "ri3", d->clientnum, lastmillis-game::maptime, weap);
             }
-            client::addmsg(N_WSELECT, "ri3", d->clientnum, lastmillis-game::maptime, weap);
+            playsound(WSND(weap, S_W_SWITCH), d->o, d, 0, -1, -1, -1, &d->wschan);
+            return true;
         }
-        playsound(WSND(weap, S_W_SWITCH), d->o, d, 0, -1, -1, -1, &d->wschan);
-        d->weapswitch(weap, lastmillis, weaponswitchdelay);
-        return true;
+        return false;
     }
 
     bool weapreload(gameent *d, int weap, int load, int ammo, bool local)
@@ -240,8 +249,11 @@ namespace weapons
             v.z = z > 0 ? v.z/z : 0;
             dest = to;
             dest.add(v);
-            vec dir = vec(dest).sub(from).normalize();
-            raycubepos(from, dir, dest, 0, RAY_CLIPMAT|RAY_ALPHAPOLY);
+            if(dest != from)
+            {
+                vec dir = vec(dest).sub(from).normalize();
+                raycubepos(from, dir, dest, 0, RAY_CLIPMAT|RAY_ALPHAPOLY);
+            }
             return;
         }
     }
diff --git a/src/game/weapons.h b/src/game/weapons.h
index 22767c0..577484c 100644
--- a/src/game/weapons.h
+++ b/src/game/weapons.h
@@ -459,10 +459,10 @@ WPFVARK(IDF_GAMEMOD,  headmin, 0, FVAR_MAX,
     0.0f,       0.0f,       0.0f,       0.0f,       0.0f,       4.0f,       4.0f,       4.0f,       4.0f,       8.0f,       8.0f,       8.0f,       0.0f
 );
 WPFVARK(IDF_GAMEMOD,  hitpush, FVAR_MIN, FVAR_MAX,
-    100.0f,     35.0f,      50.0f,      20.0f,      50.0f,      5.0f,       20.0f,      20.0f,      50.0f,      125.0f,     0.0f,       250.0f,     100.0f,
-    200.0f,     35.0f,      100.0f,     25.0f,      15.0f,      50.0f,     -75.0f,      20.0f ,     100.0f,     125.0f,     0.0f,       250.0f,     200.0f,
-    100.0f,     35.0f,      50.0f,      20.0f,      50.0f,      5.0f,       20.0f,      20.0f,      20.0f,      125.0f,     0.0f,       250.0f,     100.0f,
-    200.0f,     35.0f,      100.0f,     25.0f,      15.0f,      50.0f,     -75.0f,      20.0f,      20.0f,      125.0f,     0.0f,       250.0f,     200.0f
+    50.0f,      20.0f,      25.0f,      10.0f,      25.0f,      5.0f,       10.0f,      10.0f,      25.0f,      60.0f,      0.0f,       125.0f,     50.0f,
+    100.0f,     20.0f,      50.0f,      15.0f,      10.0f,      25.0f,     -50.0f,      10.0f ,     50.0f,      60.0f,      0.0f,       125.0f,     100.0f,
+    50.0f,      20.0f,      25.0f,      10.0f,      25.0f,      5.0f,       10.0f,      10.0f,      10.0f,      60.0f,      0.0f,       125.0f,     50.0f,
+    100.0f,     20.0f,      50.0f,      15.0f,      10.0f,      25.0f,     -50.0f,      10.0f,      10.0f,      60.0f,      0.0f,       125.0f,     100.0f
 );
 WPFVARK(IDF_GAMEMOD,  hitvel, FVAR_MIN, FVAR_MAX,
     0,          0,          0,          0,          0,          0,          0,          0,          0,          0,          0,          0,          0,
diff --git a/src/install/win/redeclipse.nsi b/src/install/win/redeclipse.nsi
index 2cb340e..3a21bc3 100644
--- a/src/install/win/redeclipse.nsi
+++ b/src/install/win/redeclipse.nsi
@@ -1,9 +1,9 @@
 ; --------------------------------
 ; Name and file
   Name "Red Eclipse"
-  VIProductVersion "1.5.5.0"
+  VIProductVersion "1.5.6.0"
   !define MajorMinorVer "1.5.x"
-  OutFile "redeclipse_1.5.5_win.exe"
+  OutFile "redeclipse_1.5.6_win.exe"
   VIAddVersionKey "OriginalFilename" $OutFile
 ; --------------------------------
 ; Installer information
diff --git a/src/semdeploy.sh b/src/semdeploy.sh
index 76b81ca..c80f1a9 100644
--- a/src/semdeploy.sh
+++ b/src/semdeploy.sh
@@ -6,6 +6,7 @@ SEMABUILD_TARGET='qreeves at icculus.org:/webspace/redeclipse.net/files'
 SEMABUILD_APT='DEBIAN_FRONTEND=noninteractive apt-get'
 SEMABUILD_MODULES=`curl --silent --fail http://redeclipse.net/files/stable/mods.txt` || exit 1
 SEMABUILD_ALLMODS="base ${SEMABUILD_MODULES}"
+SEMABUILD_DIST="bz2 combined win mac"
 
 sudo ${SEMABUILD_APT} update || exit 1
 sudo ${SEMABUILD_APT} -fy install build-essential unzip zip nsis nsis-common mktorrent || exit 1
@@ -32,20 +33,30 @@ for i in ${SEMABUILD_ALLMODS}; do
     popd
 done
 
-pushd "${SEMABUILD_BUILD}/src" || exit 1
-make dist dist-torrents || exit 1
-popd
+rm -rf "${SEMABUILD_PWD}/data" "${SEMABUILD_PWD}/.git"
 
-pushd "${SEMABUILD_BUILD}" || exit 1
-mkdir -p releases || exit 1
-mv -vf redeclipse_*.*_*.tar.bz2 releases/ || exit 1
-mv -vf redeclipse_*.*_*.exe releases/ || exit 1
-mv -vf redeclipse_*.*_*.torrent releases/ || exit 1
-pushd "${SEMABUILD_BUILD}/releases"
-for i in redeclipse_*.*; do
-    shasum "${i}" > "${i}.shasum"
-    md5sum "${i}" > "${i}.md5sum"
-done
-popd
-${SEMABUILD_SCP} -r "releases" "${SEMABUILD_TARGET}" || exit 1
-popd
+for i in ${SEMABUILD_DIST}; do
+    pushd "${SEMABUILD_BUILD}/src" || exit 1
+    make dist-${i} dist-torrent-${i} || exit 1
+    popd
+    pushd "${SEMABUILD_BUILD}" || exit 1
+    mkdir -p releases || exit 1
+    case "${i}" in
+        win)
+            mv -vf redeclipse_*.*_*.exe releases/ || exit 1
+            ;;
+        *)
+            mv -vf redeclipse_*.*_*.tar.bz2 releases/ || exit 1
+            ;;
+    esac
+    mv -vf redeclipse_*.*_*.torrent releases/ || exit 1
+    pushd "${SEMABUILD_BUILD}/releases"
+    for j in redeclipse_*.*; do
+        shasum "${j}" > "${j}.shasum"
+        md5sum "${j}" > "${j}.md5sum"
+    done
+    popd
+    ${SEMABUILD_SCP} -r "releases" "${SEMABUILD_TARGET}" || exit 1
+    rm -rf releases
+    popd
+done
\ No newline at end of file
diff --git a/src/shared/geom.h b/src/shared/geom.h
index b12956d..f2c8d70 100644
--- a/src/shared/geom.h
+++ b/src/shared/geom.h
@@ -25,6 +25,7 @@ struct vec2
     float squaredlen() const { return dot(*this); }
     float magnitude() const  { return sqrtf(squaredlen()); }
     vec2 &normalize() { mul(1/magnitude()); return *this; }
+    vec2 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; }
     float cross(const vec2 &o) const { return x*o.y - y*o.x; }
 
     vec2 &mul(float f)       { x *= f; y *= f; return *this; }
@@ -84,7 +85,7 @@ struct vec
     explicit vec(const vec4 &v);
     explicit vec(const ivec &v);
 
-    vec(float yaw, float pitch) : x(-sinf(yaw)*cosf(pitch)), y(cosf(yaw)*cosf(pitch)), z(sinf(pitch)) { if(!iszero()) normalize(); }
+    vec(float yaw, float pitch) : x(-sinf(yaw)*cosf(pitch)), y(cosf(yaw)*cosf(pitch)), z(sinf(pitch)) {}
 
     float &operator[](int i)       { return v[i]; }
     float  operator[](int i) const { return v[i]; }
@@ -122,6 +123,7 @@ struct vec
     float magnitude2() const { return sqrtf(dot2(*this)); }
     float magnitude() const  { return sqrtf(squaredlen()); }
     vec &normalize()         { div(magnitude()); return *this; }
+    vec &safenormalize()     { float m = magnitude(); if(m) div(m); return *this; }
     bool isnormalized() const { float m = squaredlen(); return (m>0.99f && m<1.01f); }
     float squaredist(const vec &e) const { return vec(*this).sub(e).squaredlen(); }
     float dist(const vec &e) const { vec t; return dist(e, t); }
@@ -283,6 +285,7 @@ struct vec4
     float magnitude() const  { return sqrtf(squaredlen()); }
     float magnitude3() const { return sqrtf(dot3(*this)); }
     vec4 &normalize() { mul(1/magnitude()); return *this; }
+    vec4 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; }
 
     vec4 &lerp(const vec4 &b, float t)
     {
diff --git a/src/shared/iengine.h b/src/shared/iengine.h
index 414ddd0..0a70f8d 100644
--- a/src/shared/iengine.h
+++ b/src/shared/iengine.h
@@ -198,11 +198,6 @@ extern int text_visible(const char *str, float hitx, float hity, int maxwidth =
 extern void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth, int flags = 0, float linespace = 0);
 extern float key_widthf(const char *str);
 
-static inline int text_width(const char *str, int xpad = 0, int ypad = 0, int flags = 0, float linespace = 0)
-{
-    return int(ceil(text_widthf(str, xpad, ypad, flags, linespace)));
-}
-
 static inline void text_bounds(const char *str, int &width, int &height, int xpad = 0, int ypad = 0, int maxwidth = -1, int flags = 0, float linespace = 0)
 {
     float widthf, heightf;
@@ -545,7 +540,7 @@ extern vector<serverinfo *> servers;
 
 extern void sendclientpacket(ENetPacket *packet, int chan);
 extern void flushclient();
-extern void disconnect(int onlyclean = 0, int async = 0);
+extern void disconnect(bool onlyclean = false, bool async = false);
 extern bool multiplayer(bool msg = true);
 extern void neterr(const char *s);
 extern void gets2c();
diff --git a/src/shared/igame.h b/src/shared/igame.h
index 4c22df8..9145e12 100644
--- a/src/shared/igame.h
+++ b/src/shared/igame.h
@@ -101,6 +101,7 @@ namespace game
     extern void updateworld();
     extern void newmap(int size, const char *mname = "");
     extern void resetmap(bool empty);
+    extern void savemap(bool force = false, const char *mname = "");
     extern void startmap(bool empty = false);
     extern bool allowmove(physent *d);
     extern dynent *iterdynents(int i, bool all = false);
diff --git a/version.txt b/version.txt
index dc047ca..ae52215 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-630847a4862514035c1de0f6eafb5af43dcb522e
+439365b7104e23880da027cabd211113e4234f44

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/redeclipse.git



More information about the Pkg-games-commits mailing list