diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6c2b730..d1d2c0d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -141,12 +141,17 @@ jobs:
 
   macos:
     name: macOS ${{ matrix.arch }} ${{ matrix.build_type }}
-    runs-on: macos-12
+    runs-on: ${{ matrix.runner }}
     strategy:
       fail-fast: false
       matrix:
-        arch: [x86_64]
+        runner: [macos-12, macos-14]
         build_type: [Debug, Release]
+        include:
+          - runner: macos-12
+            arch: x86_64
+          - runner: macos-14
+            arch: arm64
 
     steps:
       - uses: actions/checkout@v3
@@ -195,6 +200,7 @@ jobs:
           mv ./japp-linux-x86-Release/* japp-linux-x86.tar.gz
           mv ./japp-linux-x86_64-Release/* japp-linux-x86_64.tar.gz
           mv ./japp-macos-x86_64-Release/* japp-macos-x86_64.tar.gz
+          mv ./japp-macos-arm64-Release/* japp-macos-arm64.tar.gz
 
       - name: Create latest build
         uses: marvinpinto/action-automatic-releases@latest
@@ -234,6 +240,10 @@ jobs:
             artifact_name: japp-macos-x86_64.tar.gz
             zip: false
 
+          - artifact_dir: japp-macos-arm64-Release
+            artifact_name: japp-macos-arm64.tar.gz
+            zip: false
+
     steps:
       - uses: actions/checkout@v3
         with:
diff --git a/README.md b/README.md
index 764c62a..c158646 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
 # ja++
 
-ja++ modification for jedi academy - best used with [openjk](http://github.com/JACoders/OpenJK)  
-see [japp.jkhub.org](http://japp.jkhub.org) for more information
+ja++ modification for jedi academy - best used with [openjk](https://github.com/JACoders/OpenJK)  
+see [japp.jkhub.org](https://japp.jkhub.org) for more information.
+
+assets can be found here: [Razish/japp-assets](https://github.com/Razish/japp-assets)
 
 [![build](https://github.com/Razish/japp/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/Razish/japp/releases/tag/latest)
 
@@ -11,35 +13,57 @@ see [japp.jkhub.org](http://japp.jkhub.org) for more information
 | - | - | - | - |
 | x86 | ✅ | ✅ | ❓ |
 | x86_64 | ✅ | ✅ | ✅ |
-| Arm (RPi) | ❌ | ✅ | ❌ |
-| Apple Silicon | ❌ | ❌ | ✅ |
+| armhf | ❌ | ✅ | ❌ |
+| arm64 | ❌ | ✅ | ✅ |
 
 ## development requirements (general)
 
 - Python 3.11
 - [Scons](https://github.com/SCons/scons) 4.4
+- zip or 7zip on your `PATH` (for packaging)
 
 ### windows
 
-[TDM-GCC](https://jmeubank.github.io/tdm-gcc/) or MSVC (if you pass `tools=default` to scons)
+[TDM-GCC](https://jmeubank.github.io/tdm-gcc/) or Visual Studio (if you pass `tools=default` to scons)
 
 ### linux (debian-based)
 
-- `git scons gcc g++ libreadline-dev`
-- [asdf-vm](https://asdf-vm.com/guide/getting-started.html) (optional, recommended)
+install packages: `git scons gcc g++ libreadline-dev libglib2.0-dev libgtk2.0-dev libnotify-dev`
+
+### asdf-vm + lua setup (optional, recommended)
+
+install [asdf-vm](https://asdf-vm.com/guide/getting-started.html):
+
+```sh
+git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
+```
+
+add the following to your shell rc (e.g. `~/.bashrc`) and restart your shell:
+
+```sh
+. "$HOME/.asdf/asdf.sh"
+```
+
+```sh
+asdf plugin-add python
+asdf plugin-add lua https://github.com/Stratus3D/asdf-lua.git
+asdf install # install required versions
+luarocks install luafilesystem
+luarocks install luacheck
+```
 
 ## compiling
 
-just run `scons` or `build.sh`
+just run `scons` or `build.sh` followed by `lua package.lua`
 
-Options:
+options:
 
 - `force32` 1 to build a 32-bit binary on a 64-bit machine
 - `debug` 1 to generate debug information, 2 to also optimise code
 - `no_sql` 1 to disable MySQL/SQLite support
 - `no_crashhandler` 1 to disable the crash handler/logger functionality
 
-Environment Variables
+environment variables:
 
 - `NO_SSE` 1 to not generate SSE2 instructions - closer to basejka. This is used for official builds
 - `MORE_WARNINGS` 1 to enable more compiler warnings
@@ -49,7 +73,7 @@ Environment Variables
 - Raz0r (lead)
 - AstralSerpent
 - Ensiform
-- EpicLoyd
+- Exmirai
 - Morabis
 - teh
 
diff --git a/SConstruct b/SConstruct
index d3820c4..e633e5b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -107,7 +107,7 @@ if bits == 32:
     else:
         raise RuntimeError("unexpected platform: " + target_plat)
 elif bits == 64:
-    if platform.machine()[:3] == "arm":
+    if platform.machine()[:3] == "arm" or platform.machine()[:5] == "aarch":
         arch = "arm64"
     else:
         arch = "x86_64"
@@ -149,9 +149,9 @@ colours["orange"] = "\033[33m" if enableColours else ""
 colours["green"] = "\033[92m" if enableColours else ""
 colours["end"] = "\033[0m" if enableColours else ""
 
-env["SHCCCOMSTR"] = env["SHCXXCOMSTR"] = env["CCCOMSTR"] = env[
-    "CXXCOMSTR"
-] = f"{colours['cyan']} compiling: {colours['white']}$SOURCE{colours['end']}"
+env["SHCCCOMSTR"] = env["SHCXXCOMSTR"] = env["CCCOMSTR"] = env["CXXCOMSTR"] = (
+    f"{colours['cyan']} compiling: {colours['white']}$SOURCE{colours['end']}"
+)
 env["ARCOMSTR"] = f"{colours['orange']} archiving: {colours['white']}$TARGET{colours['end']}"
 env["RANLIBCOMSTR"] = f"{colours['orange']}  indexing: {colours['white']}$TARGET{colours['end']}"
 env["ASCOMSTR"] = f"{colours['orange']}assembling: {colours['white']}$TARGET{colours['end']}"
@@ -173,21 +173,13 @@ def get_compiler_version():
 ccversion = get_compiler_version()
 
 
-# git revision
-# TODO: consider tags
-def get_git_revision():
-    cmd_status, rawrevision = run_command("git rev-parse --short HEAD")
-    git_revision = None if cmd_status else rawrevision
-
-    if git_revision:
-        cmd_status, _ = run_command("git diff-index --quiet HEAD")
-        if cmd_status:
-            git_revision += "*"
-
-    return git_revision
+# git tag
+def get_git_tag():
+    cmd_status, rawtag = run_command("git describe --tags --exclude=latest")
+    return None if cmd_status else rawtag
 
 
-revision = get_git_revision()
+git_tag = get_git_tag()
 
 
 # set job/thread count
@@ -215,7 +207,7 @@ env.SetOption("num_jobs", GetNumCores())
 # notify the user of the build configuration
 if not env.GetOption("clean"):
     # build tools
-    msg = "Building " + ((revision + " ") if revision else "")
+    msg = "Building " + ((git_tag + " ") if git_tag else "")
     msg += (
         "using "
         + str(env.GetOption("num_jobs"))
@@ -652,8 +644,8 @@ if debug:
         "_DEBUG",
     ]
 
-if revision:
-    env["CPPDEFINES"] += ['REVISION=\\"' + revision + '\\"']
+if git_tag:
+    env["CPPDEFINES"] += ['GIT_TAG=\\"' + git_tag + '\\"']
 
 # override options
 if target_plat != "Linux":
diff --git a/cgame/cg_draw.cpp b/cgame/cg_draw.cpp
index 12ff9a1..218cdd7 100644
--- a/cgame/cg_draw.cpp
+++ b/cgame/cg_draw.cpp
@@ -3294,7 +3294,7 @@ static float CG_DrawTeamOverlay(float y, qboolean right, qboolean upper) {
             } else {
                 xx = x + w - TINYCHAR_WIDTH * cgs.widthRatioCoef;
             }
-            for (j = 0; j <= PW_NUM_POWERUPS; j++) {
+            for (j = 0; j < PW_NUM_POWERUPS; j++) {
                 if (ci->powerups & (1 << j)) {
 
                     item = BG_FindItemForPowerup((powerup_e)j);
diff --git a/cgame/cg_effects.cpp b/cgame/cg_effects.cpp
index 86aa67d..1c1c6d1 100644
--- a/cgame/cg_effects.cpp
+++ b/cgame/cg_effects.cpp
@@ -6,115 +6,6 @@
 #include "cg_local.h"
 #include "cg_media.h"
 
-// Bullets shot underwater
-void CG_BubbleTrail(vector3 *start, vector3 *end, float spacing) {
-    vector3 move;
-    vector3 vec;
-    float len;
-    int i;
-
-#if 0
-	if ( cg_noProjectileTrail.integer ) {
-		return;
-	}
-#endif
-
-    VectorCopy(start, &move);
-    VectorSubtract(end, start, &vec);
-    len = VectorNormalize(&vec);
-
-    // advance a random amount first
-    i = rand() % (int)spacing;
-    VectorMA(&move, i, &vec, &move);
-
-    VectorScale(&vec, spacing, &vec);
-
-    for (; i < len; i += spacing) {
-        localEntity_t *le;
-        refEntity_t *re;
-
-        le = CG_AllocLocalEntity();
-        le->leFlags = LEF_PUFF_DONT_SCALE;
-        le->leType = LE_MOVE_SCALE_FADE;
-        le->startTime = cg.time;
-        le->endTime = cg.time + 1000 + random() * 250;
-        le->lifeRate = 1.0f / (le->endTime - le->startTime);
-
-        re = &le->refEntity;
-        re->shaderTime = cg.time / 1000.0f;
-
-        re->reType = RT_SPRITE;
-        re->rotation = 0;
-        re->radius = 3;
-        re->customShader = 0; // media.gfx.world.waterBubble;
-        re->shaderRGBA[0] = 0xff;
-        re->shaderRGBA[1] = 0xff;
-        re->shaderRGBA[2] = 0xff;
-        re->shaderRGBA[3] = 0xff;
-
-        le->color[3] = 1.0f;
-
-        le->pos.trType = TR_LINEAR;
-        le->pos.trTime = cg.time;
-        VectorCopy(&move, &le->pos.trBase);
-        le->pos.trDelta.x = crandom() * 5;
-        le->pos.trDelta.y = crandom() * 5;
-        le->pos.trDelta.z = crandom() * 5 + 6;
-
-        VectorAdd(&move, &vec, &move);
-    }
-}
-
-// Adds a smoke puff or blood trail localEntity.
-localEntity_t *CG_SmokePuff(const vector3 *p, const vector3 *vel, float radius, float r, float g, float b, float a, float duration, int startTime,
-                            int fadeInTime, uint32_t leFlags, qhandle_t hShader) {
-    static int seed = 0x92;
-    localEntity_t *le;
-    refEntity_t *re;
-    //	int fadeInTime = startTime + duration / 2;
-
-    le = CG_AllocLocalEntity();
-    le->leFlags = leFlags;
-    le->radius = radius;
-
-    re = &le->refEntity;
-    re->rotation = Q_random(&seed) * 360;
-    re->radius = radius;
-    re->shaderTime = startTime / 1000.0f;
-
-    le->leType = LE_MOVE_SCALE_FADE;
-    le->startTime = startTime;
-    le->fadeInTime = fadeInTime;
-    le->endTime = startTime + duration;
-    if (fadeInTime > startTime) {
-        le->lifeRate = 1.0f / (le->endTime - le->fadeInTime);
-    } else {
-        le->lifeRate = 1.0f / (le->endTime - le->startTime);
-    }
-    le->color[0] = r;
-    le->color[1] = g;
-    le->color[2] = b;
-    le->color[3] = a;
-
-    le->pos.trType = TR_LINEAR;
-    le->pos.trTime = startTime;
-    VectorCopy(vel, &le->pos.trDelta);
-    VectorCopy(p, &le->pos.trBase);
-
-    VectorCopy(p, &re->origin);
-    re->customShader = hShader;
-
-    re->shaderRGBA[0] = le->color[0] * 0xff;
-    re->shaderRGBA[1] = le->color[1] * 0xff;
-    re->shaderRGBA[2] = le->color[2] * 0xff;
-    re->shaderRGBA[3] = 0xff;
-
-    re->reType = RT_SPRITE;
-    re->radius = le->radius;
-
-    return le;
-}
-
 void CG_TestLine(vector3 *start, vector3 *end, int time, uint32_t color, int radius) {
     localEntity_t *le;
     refEntity_t *re;
@@ -148,40 +39,6 @@ void CG_TestLine(vector3 *start, vector3 *end, int time, uint32_t color, int rad
     // re->renderfx |= RF_DEPTHHACK;
 }
 
-void CG_ThrowChunk(vector3 *origin, vector3 *velocity, qhandle_t hModel, int optionalSound, int startalpha) {
-    localEntity_t *le;
-    refEntity_t *re;
-
-    le = CG_AllocLocalEntity();
-    re = &le->refEntity;
-
-    le->leType = LE_FRAGMENT;
-    le->startTime = cg.time;
-    le->endTime = le->startTime + 5000 + random() * 3000;
-
-    VectorCopy(origin, &re->origin);
-    AxisCopy(axisDefault, re->axis);
-    re->hModel = hModel;
-
-    le->pos.trType = TR_GRAVITY;
-    le->angles.trType = TR_GRAVITY;
-    VectorCopy(origin, &le->pos.trBase);
-    VectorCopy(velocity, &le->pos.trDelta);
-    VectorSet(&le->angles.trBase, 20, 20, 20);
-    VectorCopy(velocity, &le->angles.trDelta);
-    le->pos.trTime = cg.time;
-    le->angles.trTime = cg.time;
-
-    le->leFlags = LEF_TUMBLE;
-
-    le->angles.trBase.yaw = 180;
-
-    le->bounceFactor = 0.3f;
-    le->bounceSound = optionalSound;
-
-    le->forceAlpha = startalpha;
-}
-
 //----------------------------
 //
 // Breaking Glass Technology
@@ -546,63 +403,6 @@ void CG_GlassShatter(int entnum, vector3 *dmgPt, vector3 *dmgDir, float dmgRadiu
     // otherwise something awful has happened.
 }
 
-// Throws glass shards from within a given bounding box in the world
-void CG_GlassShatter_Old(int entnum, vector3 *org, vector3 *mins, vector3 *maxs) {
-    vector3 velocity, a, shardorg, dif, difx;
-    float windowmass;
-    float shardsthrow = 0;
-    char chunkname[256];
-
-    trap->S_StartSound(org, entnum, CHAN_BODY, trap->S_RegisterSound("sound/effects/glassbreak1.wav"));
-
-    VectorSubtract(maxs, mins, &a);
-
-    windowmass = VectorLength(&a); // should give us some idea of how big the chunk of glass is
-
-    while (shardsthrow < windowmass) {
-        velocity.x = crandom() * 150;
-        velocity.y = crandom() * 150;
-        velocity.z = 150 + crandom() * 75;
-
-        Com_sprintf(chunkname, sizeof(chunkname), "models/chunks/glass/glchunks_%i.md3", Q_irand(1, 6));
-        VectorCopy(org, &shardorg);
-
-        dif.x = (maxs->x - mins->x) / 2;
-        dif.y = (maxs->y - mins->y) / 2;
-        dif.z = (maxs->z - mins->z) / 2;
-
-        if (dif.x < 2)
-            dif.x = 2;
-        if (dif.y < 2)
-            dif.y = 2;
-        if (dif.z < 2)
-            dif.z = 2;
-
-        difx.x = Q_irand(1, (dif.x * 0.9f) * 2);
-        difx.y = Q_irand(1, (dif.y * 0.9f) * 2);
-        difx.z = Q_irand(1, (dif.z * 0.9f) * 2);
-
-        if (difx.x > dif.x)
-            shardorg.x += difx.x - (dif.x);
-        else
-            shardorg.x -= difx.x;
-        if (difx.y > dif.y)
-            shardorg.y += difx.y - (dif.y);
-        else
-            shardorg.y -= difx.y;
-        if (difx.z > dif.z)
-            shardorg.z += difx.z - (dif.z);
-        else
-            shardorg.z -= difx.z;
-
-        // CG_TestLine(org, shardorg, 5000, 0xFF0000u, 3);
-
-        CG_ThrowChunk(&shardorg, &velocity, trap->R_RegisterModel(chunkname), 0, 254);
-
-        shardsthrow += 10;
-    }
-}
-
 #define DEBRIS_SPECIALCASE_ROCK -1
 #define DEBRIS_SPECIALCASE_CHUNKS -2
 #define DEBRIS_SPECIALCASE_WOOD -3
@@ -618,110 +418,6 @@ int dbModels_Wood[NUM_DEBRIS_MODELS_WOOD];
 int dbModels_Chunks[NUM_DEBRIS_MODELS_CHUNKS];
 int dbModels_Rocks[NUM_DEBRIS_MODELS_ROCKS];
 
-// Throws specified debris from within a given bounding box in the world
-void CG_CreateDebris(int entnum, vector3 *org, vector3 *mins, vector3 *maxs, int debrissound, int debrismodel) {
-    vector3 velocity, a, shardorg, dif, difx;
-    float windowmass;
-    float shardsthrow = 0;
-    int omodel = debrismodel;
-
-    if (omodel == DEBRIS_SPECIALCASE_GLASS && !dbModels_Glass[0]) { // glass no longer exists, using it for metal.
-        dbModels_Glass[0] = trap->R_RegisterModel("models/chunks/metal/metal1_1.md3");
-        dbModels_Glass[1] = trap->R_RegisterModel("models/chunks/metal/metal1_2.md3");
-        dbModels_Glass[2] = trap->R_RegisterModel("models/chunks/metal/metal1_3.md3");
-        dbModels_Glass[3] = trap->R_RegisterModel("models/chunks/metal/metal1_4.md3");
-        dbModels_Glass[4] = trap->R_RegisterModel("models/chunks/metal/metal2_1.md3");
-        dbModels_Glass[5] = trap->R_RegisterModel("models/chunks/metal/metal2_2.md3");
-        dbModels_Glass[6] = trap->R_RegisterModel("models/chunks/metal/metal2_3.md3");
-        dbModels_Glass[7] = trap->R_RegisterModel("models/chunks/metal/metal2_4.md3");
-    }
-    if (omodel == DEBRIS_SPECIALCASE_WOOD && !dbModels_Wood[0]) {
-        dbModels_Wood[0] = trap->R_RegisterModel("models/chunks/crate/crate1_1.md3");
-        dbModels_Wood[1] = trap->R_RegisterModel("models/chunks/crate/crate1_2.md3");
-        dbModels_Wood[2] = trap->R_RegisterModel("models/chunks/crate/crate1_3.md3");
-        dbModels_Wood[3] = trap->R_RegisterModel("models/chunks/crate/crate1_4.md3");
-        dbModels_Wood[4] = trap->R_RegisterModel("models/chunks/crate/crate2_1.md3");
-        dbModels_Wood[5] = trap->R_RegisterModel("models/chunks/crate/crate2_2.md3");
-        dbModels_Wood[6] = trap->R_RegisterModel("models/chunks/crate/crate2_3.md3");
-        dbModels_Wood[7] = trap->R_RegisterModel("models/chunks/crate/crate2_4.md3");
-    }
-    if (omodel == DEBRIS_SPECIALCASE_CHUNKS && !dbModels_Chunks[0]) {
-        dbModels_Chunks[0] = trap->R_RegisterModel("models/chunks/generic/chunks_1.md3");
-        dbModels_Chunks[1] = trap->R_RegisterModel("models/chunks/generic/chunks_2.md3");
-    }
-    if (omodel == DEBRIS_SPECIALCASE_ROCK && !dbModels_Rocks[0]) {
-        dbModels_Rocks[0] = trap->R_RegisterModel("models/chunks/rock/rock1_1.md3");
-        dbModels_Rocks[1] = trap->R_RegisterModel("models/chunks/rock/rock1_2.md3");
-        dbModels_Rocks[2] = trap->R_RegisterModel("models/chunks/rock/rock1_3.md3");
-        dbModels_Rocks[3] = trap->R_RegisterModel("models/chunks/rock/rock1_4.md3");
-        /*
-        dbModels_Rocks[4] = trap->R_RegisterModel("models/chunks/rock/rock2_1.md3");
-        dbModels_Rocks[5] = trap->R_RegisterModel("models/chunks/rock/rock2_2.md3");
-        dbModels_Rocks[6] = trap->R_RegisterModel("models/chunks/rock/rock2_3.md3");
-        dbModels_Rocks[7] = trap->R_RegisterModel("models/chunks/rock/rock2_4.md3");
-        dbModels_Rocks[8] = trap->R_RegisterModel("models/chunks/rock/rock3_1.md3");
-        dbModels_Rocks[9] = trap->R_RegisterModel("models/chunks/rock/rock3_2.md3");
-        dbModels_Rocks[10] = trap->R_RegisterModel("models/chunks/rock/rock3_3.md3");
-        dbModels_Rocks[11] = trap->R_RegisterModel("models/chunks/rock/rock3_4.md3");
-        */
-    }
-
-    VectorSubtract(maxs, mins, &a);
-
-    windowmass = VectorLength(&a); // should give us some idea of how big the chunk of glass is
-
-    while (shardsthrow < windowmass) {
-        velocity.x = crandom() * 150;
-        velocity.y = crandom() * 150;
-        velocity.z = 150 + crandom() * 75;
-
-        if (omodel == DEBRIS_SPECIALCASE_GLASS)
-            debrismodel = dbModels_Glass[Q_irand(0, NUM_DEBRIS_MODELS_GLASS - 1)];
-        else if (omodel == DEBRIS_SPECIALCASE_WOOD)
-            debrismodel = dbModels_Wood[Q_irand(0, NUM_DEBRIS_MODELS_WOOD - 1)];
-        else if (omodel == DEBRIS_SPECIALCASE_CHUNKS)
-            debrismodel = dbModels_Chunks[Q_irand(0, NUM_DEBRIS_MODELS_CHUNKS - 1)];
-        else if (omodel == DEBRIS_SPECIALCASE_ROCK)
-            debrismodel = dbModels_Rocks[Q_irand(0, NUM_DEBRIS_MODELS_ROCKS - 1)];
-
-        VectorCopy(org, &shardorg);
-
-        dif.x = (maxs->x - mins->x) / 2;
-        dif.y = (maxs->y - mins->y) / 2;
-        dif.z = (maxs->z - mins->z) / 2;
-
-        if (dif.x < 2)
-            dif.x = 2;
-        if (dif.y < 2)
-            dif.y = 2;
-        if (dif.z < 2)
-            dif.z = 2;
-
-        difx.x = Q_irand(1, (dif.x * 0.9f) * 2);
-        difx.y = Q_irand(1, (dif.y * 0.9f) * 2);
-        difx.z = Q_irand(1, (dif.z * 0.9f) * 2);
-
-        if (difx.x > dif.x)
-            shardorg.x += difx.x - (dif.x);
-        else
-            shardorg.x -= difx.x;
-        if (difx.y > dif.y)
-            shardorg.y += difx.y - (dif.y);
-        else
-            shardorg.y -= difx.y;
-        if (difx.z > dif.z)
-            shardorg.z += difx.z - (dif.z);
-        else
-            shardorg.z -= difx.z;
-
-        // CG_TestLine(org, shardorg, 5000, 0xFF0000u, 3);
-
-        CG_ThrowChunk(&shardorg, &velocity, debrismodel, debrissound, 0);
-
-        shardsthrow += 10;
-    }
-}
-
 // Used to find the player and shake the camera if close enough
 // intensity ranges from 1 (minor tremble) to 16 (major quake)
 void CG_ExplosionEffects(vector3 *origin, float intensity, int radius, int time) {
@@ -1071,179 +767,3 @@ void CG_ScorePlum(int client, vector3 *org, int score) {
     VectorClear(&angles);
     AnglesToAxis(&angles, re->axis);
 }
-
-localEntity_t *CG_MakeExplosion(vector3 *origin, vector3 *dir, qhandle_t hModel, int numFrames, qhandle_t shader, int msec, qboolean isSprite, float scale,
-                                uint32_t flags) {
-    float ang = 0;
-    localEntity_t *ex;
-    int offset;
-    vector3 tmpVec, newOrigin;
-
-    if (msec <= 0) {
-        trap->Error(ERR_DROP, "CG_MakeExplosion: msec = %i", msec);
-    }
-
-    // skew the time a bit so they aren't all in sync
-    offset = rand() & 63;
-
-    ex = CG_AllocLocalEntity();
-    if (isSprite) {
-        ex->leType = LE_SPRITE_EXPLOSION;
-        ex->refEntity.rotation = rand() % 360;
-        ex->radius = scale;
-        VectorScale(dir, 16, &tmpVec);
-        VectorAdd(&tmpVec, origin, &newOrigin);
-    } else {
-        ex->leType = LE_EXPLOSION;
-        VectorCopy(origin, &newOrigin);
-
-        // set axis with random rotate when necessary
-        if (!dir) {
-            AxisClear(ex->refEntity.axis);
-        } else {
-            if (!(flags & LEF_NO_RANDOM_ROTATE))
-                ang = rand() % 360;
-            VectorCopy(dir, &ex->refEntity.axis[0]);
-            RotateAroundDirection(ex->refEntity.axis, ang);
-        }
-    }
-
-    ex->startTime = cg.time - offset;
-    ex->endTime = ex->startTime + msec;
-
-    // bias the time so all shader effects start correctly
-    ex->refEntity.shaderTime = ex->startTime / 1000.0f;
-
-    ex->refEntity.hModel = hModel;
-    ex->refEntity.customShader = shader;
-    ex->lifeRate = (float)numFrames / msec;
-    ex->leFlags = flags;
-
-    // Scale the explosion
-    if (scale != 1) {
-        ex->refEntity.nonNormalizedAxes = qtrue;
-
-        VectorScale(&ex->refEntity.axis[0], scale, &ex->refEntity.axis[0]);
-        VectorScale(&ex->refEntity.axis[1], scale, &ex->refEntity.axis[1]);
-        VectorScale(&ex->refEntity.axis[2], scale, &ex->refEntity.axis[2]);
-    }
-    // set origin
-    VectorCopy(&newOrigin, &ex->refEntity.origin);
-    VectorCopy(&newOrigin, &ex->refEntity.oldorigin);
-
-    ex->color[0] = ex->color[1] = ex->color[2] = 1.0f;
-
-    return ex;
-}
-
-/*
--------------------------
-CG_SurfaceExplosion
-
-Adds an explosion to a surface
--------------------------
-*/
-
-#define NUM_SPARKS 12
-#define NUM_PUFFS 1
-#define NUM_EXPLOSIONS 4
-
-void CG_SurfaceExplosion(vector3 *origin, vector3 *normal, float radius, float shake_speed, qboolean smoke) {
-    localEntity_t *le;
-    // FXTrail			*particle;
-    vector3 direction, new_org;
-    vector3 velocity = {0, 0, 0};
-    vector3 temp_org, temp_vel;
-    int i;
-    refdef_t *refdef = CG_GetRefdef();
-
-    // Smoke
-    // Move this out a little from the impact surface
-    VectorMA(origin, 4, normal, &new_org);
-    VectorSet(&velocity, 0.0f, 0.0f, 16.0f);
-
-    for (i = 0; i < 4; i++) {
-        VectorSet(&temp_org, new_org.x + (crandom() * 16.0f), new_org.y + (crandom() * 16.0f), new_org.z + (random() * 4.0f));
-        VectorSet(&temp_vel, velocity.x + (crandom() * 8.0f), velocity.y + (crandom() * 8.0f), velocity.z + (crandom() * 8.0f));
-    }
-
-    // Core of the explosion
-
-    // Orient the explosions to face the camera
-    VectorSubtract(&refdef->vieworg, origin, &direction);
-    VectorNormalize(&direction);
-
-    // Tag the last one with a light
-    le = CG_MakeExplosion(origin, &direction, media.models.explosion, 6, media.gfx.world.surfaceExplosion, 500, qfalse, radius * 0.02f + (random() * 0.3f), 0);
-    le->light = 150;
-    VectorSet(&le->lightColor, 0.9f, 0.8f, 0.5f);
-
-    for (i = 0; i < NUM_EXPLOSIONS - 1; i++) {
-        VectorSet(&new_org, (origin->x + (16 + (crandom() * 8)) * crandom()), (origin->y + (16 + (crandom() * 8)) * crandom()),
-                  (origin->z + (16 + (crandom() * 8)) * crandom()));
-        CG_MakeExplosion(&new_org, &direction, media.models.explosion, 6, media.gfx.world.surfaceExplosion, 300 + (rand() & 99), qfalse,
-                         radius * 0.05f + (crandom() * 0.3f), 0);
-    }
-
-    // Shake the camera
-    CG_ExplosionEffects(origin, shake_speed, 350, 750);
-
-    // The level designers wanted to be able to turn the smoke spawners off.  The rationale is that they
-    //	want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark
-    //	and a smoke spewer at the explosion point...
-    if (smoke) {
-        VectorMA(origin, -8, normal, &temp_org);
-        // Impact mark
-        // FIXME: Replace mark
-        // CG_ImpactMark( media.gfx.world.burnMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse );
-    }
-}
-
-// This is the spurt of blood when a character gets hit
-void CG_Bleed(vector3 *origin, int entityNum) {
-    localEntity_t *ex;
-
-    ex = CG_AllocLocalEntity();
-    ex->leType = LE_EXPLOSION;
-
-    ex->startTime = cg.time;
-    ex->endTime = ex->startTime + 500;
-
-    VectorCopy(origin, &ex->refEntity.origin);
-    ex->refEntity.reType = RT_SPRITE;
-    ex->refEntity.rotation = rand() % 360;
-    ex->refEntity.radius = 24;
-
-    ex->refEntity.customShader = 0; // media.gfx.world.bloodExplosion;
-
-    // don't show player's own blood in view
-    if (entityNum == cg.snap->ps.clientNum) {
-        ex->refEntity.renderfx |= RF_THIRD_PERSON;
-    }
-}
-
-void CG_LaunchGib(vector3 *origin, vector3 *velocity, qhandle_t hModel) {
-    localEntity_t *le;
-    refEntity_t *re;
-
-    le = CG_AllocLocalEntity();
-    re = &le->refEntity;
-
-    le->leType = LE_FRAGMENT;
-    le->startTime = cg.time;
-    le->endTime = le->startTime + 5000 + random() * 3000;
-
-    VectorCopy(origin, &re->origin);
-    AxisCopy(axisDefault, re->axis);
-    re->hModel = hModel;
-
-    le->pos.trType = TR_GRAVITY;
-    VectorCopy(origin, &le->pos.trBase);
-    VectorCopy(velocity, &le->pos.trDelta);
-    le->pos.trTime = cg.time;
-
-    le->bounceFactor = 0.6f;
-
-    le->leBounceSoundType = LEBS_BLOOD;
-    le->leMarkType = LEMT_BLOOD;
-}
diff --git a/cgame/cg_jappScoreboard.cpp b/cgame/cg_jappScoreboard.cpp
index 8c10e42..278c4f2 100644
--- a/cgame/cg_jappScoreboard.cpp
+++ b/cgame/cg_jappScoreboard.cpp
@@ -1131,10 +1131,10 @@ static void DrawClientInfo(float fade) {
     y -= font.Height(buf);
     font.Paint(SCREEN_WIDTH - font.Width(buf), y, buf, &colour, uiTextStyle_e::ShadowedMore);
 
-#ifdef REVISION
-    y -= font.Height(REVISION);
+#ifdef GIT_TAG
+    y -= font.Height(GIT_TAG);
     // JA++ version
-    font.Paint(SCREEN_WIDTH - font.Width(REVISION), y, REVISION, &colour, uiTextStyle_e::ShadowedMore);
+    font.Paint(SCREEN_WIDTH - font.Width(GIT_TAG), y, GIT_TAG, &colour, uiTextStyle_e::ShadowedMore);
 #endif
 }
 
diff --git a/cgame/cg_local.h b/cgame/cg_local.h
index 65b92fd..b18be87 100644
--- a/cgame/cg_local.h
+++ b/cgame/cg_local.h
@@ -93,7 +93,6 @@
 #define DRAWTIMER_COUNTDOWN (0x0002u)
 #define DRAWTIMER_COLOUR (0x0004u)
 
-#define LEF_PUFF_DONT_SCALE (0x0001u)  // do not scale size over time
 #define LEF_TUMBLE (0x0002u)           // tumble over time, used for ejecting shells
 #define LEF_FADE_RGB (0x0004u)         // explicitly fade
 #define LEF_NO_RANDOM_ROTATE (0x0008u) // MakeExplosion adds random rotate which could be bad in some cases
@@ -323,25 +322,19 @@ typedef struct markPoly_s {
 } markPoly_t;
 
 enum leType_e {
-    LE_MARK,
-    LE_EXPLOSION,
-    LE_SPRITE_EXPLOSION,
-    LE_FADE_SCALE_MODEL, // currently only for Demp2 shock sphere
-    LE_FRAGMENT,
-    LE_PUFF,
-    LE_MOVE_SCALE_FADE,
-    LE_FALL_SCALE_FADE,
-    LE_FADE_RGB,
-    LE_SCALE_FADE,
-    LE_SCOREPLUM,
-    LE_OLINE,
-    LE_SHOWREFENTITY,
-    LE_LINE
+    LE_NONE,
+    LE_FADE_SCALE_MODEL, // demp2
+    LE_FRAGMENT,         // EV_DEBRIS
+    LE_PUFF,             // force push, grip
+    LE_FADE_RGB,         // strafe trail, rail trail
+    LE_SCOREPLUM,        // score plums
+    LE_OLINE,            // portable shield
+    LE_LINE,             // CG_TestLine
+    NUM_LE_TYPES
 };
+extern const stringID_table_t leTypeStrings[NUM_LE_TYPES + 1];
 
-enum leMarkType_e { LEMT_NONE, LEMT_BURN, LEMT_BLOOD }; // fragment local entities can leave marks on walls
-
-enum leBounceSoundType_e { LEBS_NONE, LEBS_BLOOD, LEBS_BRASS, LEBS_METAL, LEBS_ROCK }; // fragment local entities can make sounds on impacts
+enum leBounceSoundType_e { LEBS_NONE, LEBS_METAL, LEBS_ROCK }; // fragment local entities can make sounds on impacts
 
 typedef struct localEntity_s {
     struct localEntity_s *prev, *next;
@@ -359,7 +352,6 @@ typedef struct localEntity_s {
     float radius;
     float light;
     vector3 lightColor;
-    leMarkType_e leMarkType; // mark to leave on fragment impact
     leBounceSoundType_e leBounceSoundType;
 
     union {
@@ -507,6 +499,7 @@ typedef struct cg_s {
     qboolean levelShot; // taking a level menu screenshot
     bool haveDeferredPlayers;
     int deferredPlayerLoading;
+    bool queueLoad;
     qboolean loading;             // don't defer players at initial startup
     qboolean intermissionStarted; // don't play voice rewards, because game will end shortly
     int latestSnapshotNum;        // the number of snapshots the client system has received
@@ -862,8 +855,6 @@ void CG_AdjustEyePos(const char *modelName);
 const char *CG_Argv(int arg);
 localEntity_t *CG_AllocLocalEntity(void);
 void CG_Beam(centity_t *cent);
-void CG_Bleed(vector3 *origin, int entityNum);
-void CG_BubbleTrail(vector3 *start, vector3 *end, float spacing);
 void CG_BuildSolidList(void);
 void CG_BuildSpectatorString(void);
 void CG_CacheG2AnimInfo(char *modelName);
@@ -984,8 +975,6 @@ void CG_LoadingItem(int itemNum);
 void CG_LoadingClient(int clientNum);
 void CG_LoadMenus(const char *menuFile);
 void CG_LogPrintf(fileHandle_t fileHandle, const char *fmt, ...) Q_PRINT_FORMAT(2, 3);
-localEntity_t *CG_MakeExplosion(vector3 *origin, vector3 *dir, qhandle_t hModel, int numframes, qhandle_t shader, int msec, qboolean isSprite, float scale,
-                                uint32_t flags);
 void CG_ManualEntityRender(centity_t *cent);
 void CG_MissileHitPlayer(int weapon, vector3 *origin, vector3 *dir, int entityNum, qboolean alt_fire);
 void CG_MissileHitWall(int weapon, int clientNum, vector3 *origin, vector3 *dir, impactSound_e soundType, qboolean alt_fire, int charge);
@@ -1045,12 +1034,9 @@ void CG_Shutdown(void);
 void CG_ShutDownG2Weapons(void);
 void CG_SiegeRoundOver(centity_t *ent, int won);
 void CG_SiegeObjectiveCompleted(centity_t *ent, int won, int objectivenum);
-localEntity_t *CG_SmokePuff(const vector3 *p, const vector3 *vel, float radius, float r, float g, float b, float a, float duration, int startTime,
-                            int fadeInTime, uint32_t leFlags, qhandle_t hShader);
 void CG_Spark(vector3 *origin, vector3 *dir);
 void CG_StartMusic(qboolean bForceStart);
 qhandle_t CG_StatusHandle(int task);
-void CG_SurfaceExplosion(vector3 *origin, vector3 *normal, float radius, float shake_speed, qboolean smoke);
 vector4 *CG_TeamColor(int team);
 void CG_TestModel_f(void);
 void CG_TestGun_f(void);
diff --git a/cgame/cg_localents.cpp b/cgame/cg_localents.cpp
index b63180f..30b33a9 100644
--- a/cgame/cg_localents.cpp
+++ b/cgame/cg_localents.cpp
@@ -6,11 +6,24 @@
 
 #include "cg_local.h"
 #include "cg_media.h"
+#include "qcommon/q_shared.h"
+
+const stringID_table_t leTypeStrings[NUM_LE_TYPES + 1] = {
+    ENUM2STRING(LE_NONE),
+    ENUM2STRING(LE_FADE_SCALE_MODEL),
+    ENUM2STRING(LE_FRAGMENT),
+    ENUM2STRING(LE_PUFF),
+    ENUM2STRING(LE_FADE_RGB),
+    ENUM2STRING(LE_SCOREPLUM),
+    ENUM2STRING(LE_OLINE),
+    ENUM2STRING(LE_LINE),
+    {NULL, -1},
+};
 
 #define MAX_LOCAL_ENTITIES (2048)
-localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES];
-localEntity_t cg_activeLocalEntities; // double linked list
-localEntity_t *cg_freeLocalEntities;  // single linked list
+localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES] = {};
+localEntity_t cg_activeLocalEntities = {};     // double linked list
+localEntity_t *cg_freeLocalEntities = nullptr; // single linked list
 static uint32_t localEntitySpawnCount = 0u;
 
 // This is called at startup and for tournament restarts
@@ -74,49 +87,6 @@ localEntity_t *CG_FindLocalEntity(uint32_t id) {
 // A fragment localentity interacts with the environment in some way (hitting walls), or generates more localentities
 //	along a trail.
 
-// Leave expanding blood puffs behind gibs
-void CG_BloodTrail(localEntity_t *le) {
-    int t;
-    int t2;
-    int step;
-    vector3 newOrigin;
-    localEntity_t *blood;
-
-    step = 150;
-    t = step * ((cg.time - cg.frametime + step) / step);
-    t2 = step * (cg.time / step);
-
-    for (; t <= t2; t += step) {
-        BG_EvaluateTrajectory(&le->pos, t, &newOrigin);
-
-        blood = CG_SmokePuff(&newOrigin, &vec3_origin,
-                             20,         // radius
-                             1, 1, 1, 1, // color
-                             2000,       // trailTime
-                             t,          // startTime
-                             0,          // fadeInTime
-                             0,          // flags
-                             /*media.gfx.world.bloodTrail*/ 0);
-        // use the optimized version
-        blood->leType = LE_FALL_SCALE_FADE;
-        // drop a total of 40 units over its lifetime
-        blood->pos.trDelta.z = 40;
-    }
-}
-
-void CG_FragmentBounceMark(localEntity_t *le, trace_t *trace) {
-#if 0
-	if ( le->leMarkType == LEMT_BLOOD )
-		CG_ImpactMark( media.gfx.world.bloodMark, trace->endpos, trace->plane.normal, random()*360, 1,1,1,1, qtrue, radius, qfalse );
-	else if ( le->leMarkType == LEMT_BURN )
-		CG_ImpactMark( media.gfx.world.burnMark, trace->endpos, trace->plane.normal, random()*360, 1,1,1,1, qtrue, radius, qfalse );
-#endif
-
-    // don't allow a fragment to make multiple marks, or they
-    // pile up while settling
-    le->leMarkType = LEMT_NONE;
-}
-
 void CG_FragmentBounceSound(localEntity_t *le, trace_t *trace) {
     // half the fragments will make a bounce sounds
     if (rand() & 1) {
@@ -231,11 +201,6 @@ void CG_AddFragment(localEntity_t *le) {
 
         SE_R_AddRefEntityToScene(&le->refEntity, MAX_CLIENTS);
 
-        // add a blood trail
-        if (le->leBounceSoundType == LEBS_BLOOD) {
-            CG_BloodTrail(le);
-        }
-
         return;
     }
 
@@ -248,9 +213,6 @@ void CG_AddFragment(localEntity_t *le) {
     }
 
     if (!trace.startsolid) {
-        // leave a mark
-        CG_FragmentBounceMark(le, &trace);
-
         // do a bouncy sound
         CG_FragmentBounceSound(le, &trace);
 
@@ -311,43 +273,6 @@ static void CG_AddFadeScaleModel(localEntity_t *le) {
     SE_R_AddRefEntityToScene(ent, MAX_CLIENTS);
 }
 
-static void CG_AddMoveScaleFade(localEntity_t *le) {
-    refEntity_t *re;
-    float c;
-    vector3 delta;
-    float len;
-    refdef_t *refdef = CG_GetRefdef();
-
-    re = &le->refEntity;
-
-    if (le->fadeInTime > le->startTime && cg.time < le->fadeInTime) {
-        // fade / grow time
-        c = 1.0f - (float)(le->fadeInTime - cg.time) / (le->fadeInTime - le->startTime);
-    } else {
-        // fade / grow time
-        c = (le->endTime - cg.time) * le->lifeRate;
-    }
-
-    re->shaderRGBA[3] = 0xff * c * le->color[3];
-
-    if (!(le->leFlags & LEF_PUFF_DONT_SCALE)) {
-        re->radius = le->radius * (1.0f - c) + 8;
-    }
-
-    BG_EvaluateTrajectory(&le->pos, cg.time, &re->origin);
-
-    // if the view would be "inside" the sprite, kill the sprite
-    // so it doesn't add too much overdraw
-    VectorSubtract(&re->origin, &refdef->vieworg, &delta);
-    len = VectorLength(&delta);
-    if (len < le->radius) {
-        CG_FreeLocalEntity(le);
-        return;
-    }
-
-    SE_R_AddRefEntityToScene(re, MAX_CLIENTS);
-}
-
 static void CG_AddPuff(localEntity_t *le) {
     refEntity_t *re;
     float c;
@@ -364,73 +289,9 @@ static void CG_AddPuff(localEntity_t *le) {
     re->shaderRGBA[1] = le->color[1] * c;
     re->shaderRGBA[2] = le->color[2] * c;
 
-    if (!(le->leFlags & LEF_PUFF_DONT_SCALE)) {
-        re->radius = le->radius * (1.0f - c) + 8;
-    }
-
-    BG_EvaluateTrajectory(&le->pos, cg.time, &re->origin);
-
-    // if the view would be "inside" the sprite, kill the sprite
-    // so it doesn't add too much overdraw
-    VectorSubtract(&re->origin, &refdef->vieworg, &delta);
-    len = VectorLength(&delta);
-    if (len < le->radius) {
-        CG_FreeLocalEntity(le);
-        return;
-    }
-
-    SE_R_AddRefEntityToScene(re, MAX_CLIENTS);
-}
-
-// For rocket smokes that hang in place, fade out, and are removed if the view passes through them.
-//	There are often many of these, so it needs to be simple.
-static void CG_AddScaleFade(localEntity_t *le) {
-    refEntity_t *re;
-    float c;
-    vector3 delta;
-    float len;
-    refdef_t *refdef = CG_GetRefdef();
-
-    re = &le->refEntity;
-
-    // fade / grow time
-    c = (le->endTime - cg.time) * le->lifeRate;
-
-    re->shaderRGBA[3] = 0xff * c * le->color[3];
     re->radius = le->radius * (1.0f - c) + 8;
 
-    // if the view would be "inside" the sprite, kill the sprite
-    // so it doesn't add too much overdraw
-    VectorSubtract(&re->origin, &refdef->vieworg, &delta);
-    len = VectorLength(&delta);
-    if (len < le->radius) {
-        CG_FreeLocalEntity(le);
-        return;
-    }
-
-    SE_R_AddRefEntityToScene(re, MAX_CLIENTS);
-}
-
-// This is just an optimized CG_AddMoveScaleFade for blood mists that drift down, fade out, and are emoved if the view
-//	passes through them.
-// There are often 100+ of these, so it needs to be simple.
-static void CG_AddFallScaleFade(localEntity_t *le) {
-    refEntity_t *re;
-    float c;
-    vector3 delta;
-    float len;
-    refdef_t *refdef = CG_GetRefdef();
-
-    re = &le->refEntity;
-
-    // fade time
-    c = (le->endTime - cg.time) * le->lifeRate;
-
-    re->shaderRGBA[3] = 0xff * c * le->color[3];
-
-    re->origin.z = le->pos.trBase.z - (1.0f - c) * le->pos.trDelta.z;
-
-    re->radius = le->radius * (1.0f - c) + 16;
+    BG_EvaluateTrajectory(&le->pos, cg.time, &re->origin);
 
     // if the view would be "inside" the sprite, kill the sprite
     // so it doesn't add too much overdraw
@@ -444,73 +305,6 @@ static void CG_AddFallScaleFade(localEntity_t *le) {
     SE_R_AddRefEntityToScene(re, MAX_CLIENTS);
 }
 
-static void CG_AddExplosion(localEntity_t *ex) {
-    refEntity_t *ent;
-
-    ent = &ex->refEntity;
-
-    // add the entity
-    SE_R_AddRefEntityToScene(ent, MAX_CLIENTS);
-
-    // add the dlight
-    if (ex->light) {
-        float light;
-
-        light = (float)(cg.time - ex->startTime) / (ex->endTime - ex->startTime);
-        if (light < 0.5f) {
-            light = 1.0f;
-        } else {
-            light = 1.0f - (light - 0.5f) * 2;
-        }
-        light = ex->light * light;
-        trap->R_AddLightToScene(&ent->origin, light, ex->lightColor.r, ex->lightColor.g, ex->lightColor.b);
-    }
-}
-
-static void CG_AddSpriteExplosion(localEntity_t *le) {
-    refEntity_t re;
-    float c;
-
-    re = le->refEntity;
-
-    c = (le->endTime - cg.time) / (float)(le->endTime - le->startTime);
-    if (c > 1) {
-        c = 1.0f; // can happen during connection problems
-    }
-
-    re.shaderRGBA[0] = 0xff;
-    re.shaderRGBA[1] = 0xff;
-    re.shaderRGBA[2] = 0xff;
-    re.shaderRGBA[3] = 0xff * c * 0.33f;
-
-    re.reType = RT_SPRITE;
-    re.radius = 42 * (1.0f - c) + 30;
-
-    SE_R_AddRefEntityToScene(&re, MAX_CLIENTS);
-
-    // add the dlight
-    if (le->light) {
-        float light;
-
-        light = (float)(cg.time - le->startTime) / (le->endTime - le->startTime);
-        if (light < 0.5f) {
-            light = 1.0f;
-        } else {
-            light = 1.0f - (light - 0.5f) * 2;
-        }
-        light = le->light * light;
-        trap->R_AddLightToScene(&re.origin, light, le->lightColor.r, le->lightColor.g, le->lightColor.b);
-    }
-}
-
-void CG_AddRefEntity(localEntity_t *le) {
-    if (le->endTime < cg.time) {
-        CG_FreeLocalEntity(le);
-        return;
-    }
-    SE_R_AddRefEntityToScene(&le->refEntity, MAX_CLIENTS);
-}
-
 #define NUMBER_SIZE 8
 
 void CG_AddScorePlum(localEntity_t *le) {
@@ -640,35 +434,30 @@ void CG_AddLine(localEntity_t *le) {
 }
 
 void CG_AddLocalEntities(void) {
-    localEntity_t *next;
+    // int numAdded[NUM_LE_TYPES] = {};
 
+    localEntity_t *next;
     // walk the list backwards, so any new local entities generated (trails, marks, etc) will be present this frame
     for (localEntity_t *le = cg_activeLocalEntities.prev; le != &cg_activeLocalEntities; le = next) {
-        // grab next now, so if the local entity is freed we
-        // still have it
+        // grab next now, so if the local entity is freed we still have it
         next = le->prev;
 
         if (cg.time >= le->endTime) {
             CG_FreeLocalEntity(le);
             continue;
         }
-        switch (le->leType) {
-        case LE_MARK:
-            break;
 
-        case LE_SPRITE_EXPLOSION:
-            CG_AddSpriteExplosion(le);
-            break;
+        // numAdded[le->leType]++;
 
-        case LE_EXPLOSION:
-            CG_AddExplosion(le);
+        switch (le->leType) {
+        default:
             break;
 
         case LE_FADE_SCALE_MODEL:
             CG_AddFadeScaleModel(le);
             break;
 
-        case LE_FRAGMENT: // gibs and brass
+        case LE_FRAGMENT:
             CG_AddFragment(le);
             break;
 
@@ -676,22 +465,10 @@ void CG_AddLocalEntities(void) {
             CG_AddPuff(le);
             break;
 
-        case LE_MOVE_SCALE_FADE: // water bubbles
-            CG_AddMoveScaleFade(le);
-            break;
-
-        case LE_FADE_RGB: // teleporters, railtrails
+        case LE_FADE_RGB:
             CG_AddFadeRGB(le);
             break;
 
-        case LE_FALL_SCALE_FADE: // gib blood trails
-            CG_AddFallScaleFade(le);
-            break;
-
-        case LE_SCALE_FADE: // rocket trails
-            CG_AddScaleFade(le);
-            break;
-
         case LE_SCOREPLUM:
             CG_AddScorePlum(le);
             break;
@@ -700,13 +477,14 @@ void CG_AddLocalEntities(void) {
             CG_AddOLine(le);
             break;
 
-        case LE_SHOWREFENTITY:
-            CG_AddRefEntity(le);
-            break;
-
-        case LE_LINE: // oriented lines for FX
+        case LE_LINE:
             CG_AddLine(le);
             break;
         }
     }
+
+    // for (leType_e leType = LE_NONE; leType < NUM_LE_TYPES; leType = (leType_e)(leType + 1)) {
+    //     const char *leTypeStr = GetStringForID(leTypeStrings, leType);
+    //     Com_Printf("CG_AddLocalEntities: %s: %i\n", leTypeStr, numAdded[leType]);
+    // }
 }
diff --git a/cgame/cg_lualocalentity.cpp b/cgame/cg_lualocalentity.cpp
index 3e0be53..d60b356 100644
--- a/cgame/cg_lualocalentity.cpp
+++ b/cgame/cg_lualocalentity.cpp
@@ -160,13 +160,6 @@ static int LocalEntity_GetLightColor(lua_State *L, localEntity_t *ent) {
     return 1;
 }
 
-static void LocalEntity_SetMarkType(lua_State *L, localEntity_t *ent) { ent->leMarkType = (leMarkType_e)luaL_checkinteger(L, 3); }
-
-static int LocalEntity_GetMarkType(lua_State *L, localEntity_t *ent) {
-    lua_pushinteger(L, ent->leMarkType);
-    return 1;
-}
-
 static void LocalEntity_SetBSndType(lua_State *L, localEntity_t *ent) { ent->leBounceSoundType = (leBounceSoundType_e)luaL_checkinteger(L, 3); }
 
 static int LocalEntity_GetBSndType(lua_State *L, localEntity_t *ent) {
@@ -240,11 +233,6 @@ static const le_prop_t localEntityProperties[] = {
         LocalEntity_Line_GetWidth,
         LocalEntity_Line_SetWidth,
     },
-    {
-        "marktype",
-        LocalEntity_GetMarkType,
-        LocalEntity_SetMarkType,
-    },
     {
         "radius",
         LocalEntity_GetRadius,
diff --git a/cgame/cg_main.cpp b/cgame/cg_main.cpp
index 12c28b8..9e33344 100644
--- a/cgame/cg_main.cpp
+++ b/cgame/cg_main.cpp
@@ -334,6 +334,11 @@ static void CVU_ForceColour(void) {
 }
 
 static void CVU_ForceModel(void) {
+    if (cg.loading) {
+        cg.haveDeferredPlayers = true;
+        cg.queueLoad = true;
+        return;
+    }
     for (int i = 0; i < cgs.maxclients; i++) {
         if (VALIDSTRING(CG_ConfigString(CS_PLAYERS + i))) {
             CG_NewClientInfo(i, qtrue);
@@ -551,13 +556,6 @@ static void CVU_AccelSize(void) {
     }
 }
 
-typedef struct cvarTable_s {
-    vmCvar_t *vmCvar;
-    const char *cvarName, *defaultString;
-    void (*update)(void);
-    uint32_t cvarFlags;
-} cvarTable_t;
-
 #define XCVAR_DECL
 #include "cg_xcvar.h"
 #undef XCVAR_DECL
@@ -569,38 +567,38 @@ void CG_Set2DRatio(void) {
         cgs.widthRatioCoef = 1.0f;
 }
 
-static cvarTable_t cvarTable[] = {
+static const struct cvarTable_t {
+    vmCvar_t *vmCvar;
+    const char *cvarName, *defaultString;
+    void (*update)(void);
+    uint32_t cvarFlags;
+} cvarTable[] = {
 #define XCVAR_LIST
 #include "cg_xcvar.h"
 #undef XCVAR_LIST
 };
 
-static int cvarTableSize = ARRAY_LEN(cvarTable);
-
 void CG_RegisterCvars(void) {
-    int i;
-    cvarTable_t *cv;
-
-    for (i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++) {
-        trap->Cvar_Register(cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags);
-        if (cv->update)
-            cv->update();
+    for (const auto &cv : cvarTable) {
+        trap->Cvar_Register(cv.vmCvar, cv.cvarName, cv.defaultString, cv.cvarFlags);
+    }
+    for (const auto &cv : cvarTable) {
+        if (cv.update) {
+            cv.update();
+        }
     }
 }
 
 void CG_UpdateCvars(void) {
-    int i;
-    cvarTable_t *cv;
-
-    for (i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++) {
-        if (cv->vmCvar) {
-            int modCount = cv->vmCvar->modificationCount;
-            trap->Cvar_Update(cv->vmCvar);
-            if (cv->vmCvar->modificationCount > modCount) {
-                if (cv->update) {
-                    cv->update();
+    for (const auto &cv : cvarTable) {
+        if (cv.vmCvar) {
+            int modCount = cv.vmCvar->modificationCount;
+            trap->Cvar_Update(cv.vmCvar);
+            if (cv.vmCvar->modificationCount > modCount) {
+                if (cv.update) {
+                    cv.update();
                 }
-                JPLua::Cvar_Update(cv->cvarName);
+                JPLua::Cvar_Update(cv.cvarName);
             }
         }
     }
@@ -772,7 +770,7 @@ static void CG_RegisterClients(void) {
 
 const char *CG_ConfigString(int index) {
     // don't read configstrings before initialisation
-    assert(cgs.gameState.dataCount != 0);
+    // assert(cgs.gameState.dataCount != 0);
 
     if (index < 0 || index >= MAX_CONFIGSTRINGS) {
         trap->Error(ERR_DROP, "CG_ConfigString: bad index: %i", index);
@@ -800,11 +798,11 @@ char *CG_GetMenuBuffer(const char *filename) {
 
     len = trap->FS_Open(filename, &f, FS_READ);
     if (!f) {
-        trap->Print(va(S_COLOR_RED "menu file not found: %s, using default\n", filename));
+        trap->Print(S_COLOR_RED "menu file not found: %s, using default\n", filename);
         return NULL;
     }
     if (len >= MAX_MENUFILE) {
-        trap->Print(va(S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE));
+        trap->Print(S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE);
         trap->FS_Close(f);
         return NULL;
     }
@@ -1697,26 +1695,33 @@ static void CG_CloseLog(fileHandle_t *f) {
 // Called after every level change or subsystem restart
 // Will perform callbacks to make the loading info screen update.
 void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qboolean demoPlayback) {
-    char buf[64];
-    const char *s;
-
     // clear out globals
-    BG_InitAnimsets();
-    memset(cg_entities, 0, sizeof(cg_entities));
-
     cgs.~cgs_t();
     new (&cgs) cgs_t{}; // Using {} instead of () to work around MSVC bug
     cg.~cg_t();
     new (&cg) cg_t{};
-
+    memset(cg_entities, 0, sizeof(cg_entities));
     memset(cg_items, 0, sizeof(cg_items));
     memset(cg_weapons, 0, sizeof(cg_weapons));
+    BG_InitAnimsets();
 
-    trap->GetGameState(&cgs.gameState);
-
-    trap->RegisterSharedMemory(cg.sharedBuffer);
+    cg.clientNum = clientNum;
+    cg.demoPlayback = demoPlayback;
+    cg.forceHUDActive = qtrue;
+    cg.forceHUDNextFlashTime = 0;
+    cg.forceHUDTotalFlashTime = 0;
+    cg.forceSelect = 0xFFFFFFFFu;
+    cg.itemSelect = -1;
+    cg.weaponSelect = WP_BRYAR_PISTOL;
+    cgs.processedSnapshotNum = serverMessageNum;
+    cgs.redflag = cgs.blueflag = FLAG_ATBASE;
+    cgs.serverCommandSequence = serverCommandSequence;
 
+    CG_LoadingString("Cvars");
     CG_RegisterCvars();
+    cg.renderingThirdPerson = cg_thirdPerson.integer;
+
+    CG_LoadingString("Commands");
     CG_InitConsoleCommands();
 
     // logging
@@ -1738,45 +1743,38 @@ void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qbo
         trap->Print("Not logging security events to disk.\n");
 
     // load some permanent stuff
+    CG_LoadingString("Vehicles");
     BG_VehicleLoadParms();
     CG_InitJetpackGhoul2();
     CG_PmoveClientPointerUpdate();
 
     CG_PreloadMedia();
 
-    cg.clientNum = clientNum;
-    cgs.processedSnapshotNum = serverMessageNum;
-    cgs.serverCommandSequence = serverCommandSequence;
-    cg.itemSelect = -1;
-    cg.forceSelect = 0xFFFFFFFFu;
-    cg.forceHUDActive = qtrue;
-    cg.forceHUDTotalFlashTime = 0;
-    cg.forceHUDNextFlashTime = 0;
-    cg.renderingThirdPerson = cg_thirdPerson.integer;
-    cg.weaponSelect = WP_BRYAR_PISTOL;
-    cgs.redflag = cgs.blueflag = FLAG_ATBASE;
-    cg.demoPlayback = demoPlayback;
-    cgs.levelStartTime = atoi(CG_ConfigString(CS_LEVEL_START_TIME));
-
     trap->GetGlconfig(&cgs.glconfig);
-    cgs.screenXScale = cgs.glconfig.vidWidth / SCREEN_WIDTH;
-    cgs.screenYScale = cgs.glconfig.vidHeight / SCREEN_HEIGHT;
+    cgs.screenXScale = cgs.glconfig.vidWidth / (float)SCREEN_WIDTH;
+    cgs.screenYScale = cgs.glconfig.vidHeight / (float)SCREEN_HEIGHT;
     CG_Set2DRatio();
 
-    s = CG_ConfigString(CS_GAME_VERSION);
+    // NOTE: do not read configstrings before this point
+    trap->GetGameState(&cgs.gameState);
+
+    const char *s = CG_ConfigString(CS_GAME_VERSION);
     if (strcmp(s, GAME_VERSION)) {
         trap->Error(ERR_DROP, "Client/Server game mismatch: " GAME_VERSION "/%s", s);
         return;
     }
 
+    CG_LoadingString("Game state");
+    trap->RegisterSharedMemory(cg.sharedBuffer);
+    CG_ParseServerinfo();
+    cgs.levelStartTime = atoi(CG_ConfigString(CS_LEVEL_START_TIME));
+    Q_strncpyz(cgs.voteString, CG_ConfigString(CS_VOTE_STRING), sizeof(cgs.voteString));
+
     CG_TransitionPermanent();
 
     CG_LoadingString("Notifications");
     CG_NotifyInit();
 
-    CG_LoadingString("Server info");
-    CG_ParseServerinfo();
-
     CG_LoadingString("String pool");
     String_Init();
 
@@ -1850,6 +1848,7 @@ void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qbo
     CG_LoadingString("");
 
     // post-init stuff
+    char buf[64];
     trap->Cvar_VariableStringBuffer("rate", buf, sizeof(buf));
     if (atoi(buf) == 4000) {
         trap->Print(S_COLOR_YELLOW "WARNING: Default /rate value detected. Suggest typing /rate 25000 for a smoother "
@@ -1858,6 +1857,7 @@ void CG_Init(int serverMessageNum, int serverCommandSequence, int clientNum, qbo
 
     CG_UpdateServerHistory();
     BG_FixSaberMoveData();
+    BG_FixWeaponAttackAnim();
 }
 
 // makes sure returned string is in localized format
diff --git a/cgame/cg_media.cpp b/cgame/cg_media.cpp
index fcb94e6..2effa63 100644
--- a/cgame/cg_media.cpp
+++ b/cgame/cg_media.cpp
@@ -516,7 +516,6 @@ static const resource_t gfx[] = {
     {&media.gfx.world.sightShell, "powerups/sightshell", RFL_NONE, GTB_ALL},
     {&media.gfx.world.solidWhite, "gfx/effects/solidWhite_cull", RFL_NONE, GTB_ALL},
     {&media.gfx.world.strafeTrail, "gfx/misc/whiteline2", RFL_NOMIP, GTB_ALL},
-    {&media.gfx.world.surfaceExplosion, "surfaceExplosion", RFL_NONE, GTB_ALL},
     {&media.gfx.world.wakeMark, "wake", RFL_NONE, GTB_ALL},
     //	{ &media.gfx.world.whiteShader, NULL, RFL_NONE, GTB_ALL },
     {&media.gfx.world.yellowDroppedSaber, "gfx/effects/yellow_glow", RFL_NONE, GTB_ALL},
diff --git a/cgame/cg_media.h b/cgame/cg_media.h
index fbfe1bd..b557946 100644
--- a/cgame/cg_media.h
+++ b/cgame/cg_media.h
@@ -253,7 +253,6 @@ typedef struct cgMedia_s {
             qhandle_t sightShell;
             qhandle_t solidWhite;
             qhandle_t strafeTrail;
-            qhandle_t surfaceExplosion;
             qhandle_t wakeMark;
             qhandle_t whiteShader;
             qhandle_t yellowDroppedSaber;
diff --git a/cgame/cg_newDraw.cpp b/cgame/cg_newDraw.cpp
index 45c3a8b..e7178db 100644
--- a/cgame/cg_newDraw.cpp
+++ b/cgame/cg_newDraw.cpp
@@ -321,7 +321,7 @@ void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale
         ci = cgs.clientinfo + sortedTeamPlayers[i];
         if (ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) {
             xx = rect->x + 1;
-            for (j = 0; j <= PW_NUM_POWERUPS; j++) {
+            for (j = 0; j < PW_NUM_POWERUPS; j++) {
                 if (ci->powerups & (1 << j)) {
                     if ((item = BG_FindItemForPowerup((powerup_e)j))) {
                         CG_DrawPic(xx, y, PIC_WIDTH, PIC_WIDTH, trap->R_RegisterShader(item->icon));
diff --git a/cgame/cg_players.cpp b/cgame/cg_players.cpp
index 6249728..f882e6d 100644
--- a/cgame/cg_players.cpp
+++ b/cgame/cg_players.cpp
@@ -1302,8 +1302,7 @@ void CG_NewClientInfo(int clientNum, qboolean entitiesInitialized) {
     }
 }
 
-qboolean cgQueueLoad = qfalse;
-// Called at the beginning of CG_Player if cgQueueLoad is set.
+// Called at the beginning of CG_Player if cg.queueLoad is set.
 void CG_ActualLoadDeferredPlayers(void) {
     int i;
     clientInfo_t *ci;
@@ -1325,7 +1324,7 @@ void CG_ActualLoadDeferredPlayers(void) {
 }
 
 // Called each frame when a player is dead and the scoreboard is up so deferred players can be loaded
-void CG_LoadDeferredPlayers(void) { cgQueueLoad = qtrue; }
+void CG_LoadDeferredPlayers(void) { cg.queueLoad = qtrue; }
 
 #define FOOTSTEP_DISTANCE (32)
 static void _PlayerFootStep(const vector3 *origin, const float orientation, const float radius, centity_t *const cent, footstepType_e footStepType) {
@@ -1698,7 +1697,6 @@ void CG_PlayerAnimEvents(int animFileIndex, int eventFileIndex, qboolean torso,
         else {
             // still in same anim, check for looping anim
             animation_t *animation;
-
             inSameAnim = qtrue;
             animation = &bgAllAnims[animFileIndex].anims[anim];
             animBackward = (animation->frameLerp < 0);
@@ -2205,7 +2203,7 @@ void CG_Rag_Trace(trace_t *result, const vector3 *start, const vector3 *mins, co
     result->entityNum = result->fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
 }
 
-//#define _RAG_BOLT_TESTING
+// #define _RAG_BOLT_TESTING
 
 #ifdef _RAG_BOLT_TESTING
 void CG_TempTestFunction(centity_t *cent, vector3 *forcedAngles) {
@@ -6235,7 +6233,9 @@ static void CG_VehicleEffects(centity_t *cent) {
 #else
                 if (pVehNPC->m_pVehicleInfo->iTrailFX)
 #endif
-                { trap->FX_PlayEffectID(pVehNPC->m_pVehicleInfo->iTrailFX, &org, &fwd, -1, -1, qfalse); }
+                {
+                    trap->FX_PlayEffectID(pVehNPC->m_pVehicleInfo->iTrailFX, &org, &fwd, -1, -1, qfalse);
+                }
 
                 // do exhaust
                 if ((cent->currentState.eFlags & EF_JETPACK_ACTIVE))
@@ -6426,7 +6426,7 @@ void CG_Player(centity_t *cent) {
     vector3 rootAngles, angles, dir, elevated, enang, seekorg;
     mdxaBone_t boltMatrix, lHandMatrix;
     clientInfo_t *ci;
-    refEntity_t legs, torso;
+    refEntity_t legs = {}, torso = {};
     int clientNum, team;
     uint32_t renderfx;
 
diff --git a/cgame/cg_q3pScoreboard.cpp b/cgame/cg_q3pScoreboard.cpp
index 643600a..a50f4bb 100644
--- a/cgame/cg_q3pScoreboard.cpp
+++ b/cgame/cg_q3pScoreboard.cpp
@@ -800,10 +800,10 @@ static void DrawClientInfo(float fade) {
     VectorCopy4(&g_color_table[ColorIndex(COLOR_ORANGE)], &colour);
     colour.a = fade;
 
-#ifdef REVISION
-    y -= font.Height(REVISION);
+#ifdef GIT_TAG
+    y -= font.Height(GIT_TAG);
     // JA++ version
-    font.Paint(SCREEN_WIDTH - font.Width(REVISION), y, REVISION, &colour, uiTextStyle_e::ShadowedMore);
+    font.Paint(SCREEN_WIDTH - font.Width(GIT_TAG), y, GIT_TAG, &colour, uiTextStyle_e::ShadowedMore);
 #endif
 
     // date
diff --git a/cgame/cg_servercmds.cpp b/cgame/cg_servercmds.cpp
index a394544..69f1ba0 100644
--- a/cgame/cg_servercmds.cpp
+++ b/cgame/cg_servercmds.cpp
@@ -176,9 +176,6 @@ void CG_ParseServerinfo(void) {
     Q_strncpyz(cgs.japp.serverName, Info_ValueForKey(info, "sv_hostname"), sizeof(cgs.japp.serverName));
     CPM_UpdateSettings(!!(cgs.japp.jp_cinfo & CINFO_CPMPHYSICS));
 
-    // Fix fucked up vote strings
-    Q_strncpyz(cgs.voteString, CG_ConfigString(CS_VOTE_STRING), sizeof(cgs.voteString));
-
     // Raz: Synchronise our expected snaps/sec with the server's framerate
     //		OpenJK servers will try to match us to the sv_fps too (sv_client.cpp -> SV_UserinfoChanged)
     i = atoi(Info_ValueForKey(info, "sv_fps"));
@@ -753,8 +750,9 @@ static void CG_ConfigStringModified(void) {
     }
 
     else if (num == CS_LEGACY_FIXES) {
-        // LEGACYFIX_SABERMOVEDATA may have changed
+        // LEGACYFIX_SABERMOVEDATA etc may have changed
         BG_FixSaberMoveData();
+        BG_FixWeaponAttackAnim();
     }
 
     else if (num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3))
diff --git a/cgame/cg_smartentities.cpp b/cgame/cg_smartentities.cpp
index f0a9dc8..bba0671 100644
--- a/cgame/cg_smartentities.cpp
+++ b/cgame/cg_smartentities.cpp
@@ -214,7 +214,7 @@ qboolean SE_RenderPlayer(int targIndex) {
 
 // Tracing non-players seems to have a bad effect, we know players are limited to 32 per frame, however other gentities
 //	that are being added are not! It's stupid to actually add traces for it, even with a limited form i used before of 2
-//	traces per object. There are to many too track and simply drawing them takes less FPS either way.
+//	traces per object. There are too many to track and simply drawing them takes less FPS either way.
 qboolean SE_RenderThisEntity(vector3 *testOrigin, int gameEntity) {
     // If we do not have a snapshot, we cannot calculate anything.
     if (!cg.snap)
diff --git a/cgame/cg_view.cpp b/cgame/cg_view.cpp
index 51ce6df..e5a6c5e 100644
--- a/cgame/cg_view.cpp
+++ b/cgame/cg_view.cpp
@@ -1768,7 +1768,6 @@ float cg_linearFogOverride = 0.0f;         // designer-specified override for li
 void BG_VehicleTurnRateForSpeed(Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride);
 qboolean PM_InKnockDown(playerState_t *ps);
 
-extern qboolean cgQueueLoad;
 void CG_ActualLoadDeferredPlayers(void);
 
 static int cg_siegeClassIndex = -2;
@@ -2006,10 +2005,10 @@ void CG_DrawActiveFrame(int serverTime, stereoFrame_e stereoView, qboolean demoP
         pwSet = 1;
     }
 
-    if (cgQueueLoad || (cg.haveDeferredPlayers && cg_deferPlayers.integer == 2 && cg.snap && VectorLength(&cg.snap->ps.velocity) < 1.0f)) {
+    if (cg.queueLoad || (cg.haveDeferredPlayers && cg_deferPlayers.integer == 2 && cg.snap && VectorLength(&cg.snap->ps.velocity) < 1.0f)) {
         // do this before you start messing around with adding ghoul2 refents and crap
         CG_ActualLoadDeferredPlayers();
-        cgQueueLoad = qfalse;
+        cg.queueLoad = qfalse;
     }
 
     cg.time = serverTime;
@@ -2183,7 +2182,6 @@ void CG_DrawActiveFrame(int serverTime, stereoFrame_e stereoView, qboolean demoP
     if (!cg.hyperspace) {
         CG_AddPacketEntities(qfalse); // adter calcViewValues, so predicted player state is correct
         CG_AddMarks();
-        CG_AddLocalEntities();
         CG_DrawMiscEnts();
     }
     CG_AddViewWeapon(&cg.predictedPlayerState);
@@ -2204,6 +2202,11 @@ void CG_DrawActiveFrame(int serverTime, stereoFrame_e stereoView, qboolean demoP
         CG_AddMovementVectors();
     }
 
+    if (!cg.hyperspace) {
+        // specifically render these later, as they may overflow the refent buffer
+        CG_AddLocalEntities();
+    }
+
     refdef->time = cg.time;
     memcpy(refdef->areamask, cg.snap->areamask, sizeof(refdef->areamask));
 
diff --git a/game/NPC_stats.cpp b/game/NPC_stats.cpp
index 69eb24b..2817477 100644
--- a/game/NPC_stats.cpp
+++ b/game/NPC_stats.cpp
@@ -1855,7 +1855,7 @@ qboolean NPC_ParseParms(const char *NPCName, gentity_t *NPC) {
                 }
                 // FIXME: need to precache the weapon, too?  (in above func)
                 weap = GetIDForString(WPTable, value);
-                if (weap >= WP_NONE && weap <= WP_NUM_WEAPONS) ///*WP_BLASTER_PISTOL*/WP_SABER ) //?!
+                if (weap >= WP_NONE && weap < WP_NUM_WEAPONS) ///*WP_BLASTER_PISTOL*/WP_SABER ) //?!
                 {
                     NPC->client->ps.weapon = weap;
                     NPC->client->ps.stats[STAT_WEAPONS] |= (1 << NPC->client->ps.weapon);
diff --git a/game/bg_lua.cpp b/game/bg_lua.cpp
index f4b2ac2..240e113 100644
--- a/game/bg_lua.cpp
+++ b/game/bg_lua.cpp
@@ -1711,7 +1711,7 @@ void Init(void) {
     }
 
     // set the JPLua version
-    semver_parse("13.6.1", &jpluaVersion);
+    semver_parse(JPLUA_VERSION, &jpluaVersion);
 
     // set the callback in case of an error
     lua_atpanic(ls.L, Error);
diff --git a/game/bg_lua.h b/game/bg_lua.h
index 616e9cd..5e206b7 100644
--- a/game/bg_lua.h
+++ b/game/bg_lua.h
@@ -25,6 +25,8 @@
 
 #include "semver/semver.h"
 
+#define JPLUA_VERSION "13.6.2"
+
 namespace JPLua {
 
 struct plugin_t {
diff --git a/game/bg_misc.cpp b/game/bg_misc.cpp
index c7b3287..e0d0e62 100644
--- a/game/bg_misc.cpp
+++ b/game/bg_misc.cpp
@@ -199,7 +199,7 @@ const int WeaponReadyLegsAnim[WP_NUM_WEAPONS] = {
     BOTH_STAND1  // WP_TURRET,
 };
 
-const int WeaponAttackAnim[WP_NUM_WEAPONS] = {
+int WeaponAttackAnim[WP_NUM_WEAPONS] = {
     BOTH_ATTACK1, // WP_NONE, //(shouldn't happen)
 
     BOTH_ATTACK3,       // WP_STUN_BATON,
@@ -216,7 +216,7 @@ const int WeaponAttackAnim[WP_NUM_WEAPONS] = {
     BOTH_THERMAL_THROW, // WP_THERMAL,
     BOTH_ATTACK3,       // BOTH_ATTACK11,//WP_TRIP_MINE,
     BOTH_ATTACK3,       // BOTH_ATTACK12,//WP_DET_PACK,
-    BOTH_ATTACK3,       // WP_CONCUSSION, //Raz: Fixed bryar pistol animation
+    BOTH_ATTACK3,       // WP_CONCUSSION,
     BOTH_ATTACK2,       // WP_BRYAR_OLD,
 
     // NOT VALID (e.g. should never really be used):
@@ -225,6 +225,32 @@ const int WeaponAttackAnim[WP_NUM_WEAPONS] = {
     BOTH_ATTACK1 // WP_TURRET,
 };
 
+void BG_FixWeaponAttackAnim(void) {
+#if defined(PROJECT_GAME)
+    const qboolean doFix = !!g_fixWeaponAttackAnim.integer;
+#elif defined(PROJECT_CGAME)
+    const char *cs = CG_ConfigString(CS_LEGACY_FIXES);
+    const uint32_t legacyFixes = strtoul(cs, NULL, 0);
+    const qboolean doFix = !!(legacyFixes & (1 << LEGACYFIX_WEAPONATTACKANIM));
+#elif defined(PROJECT_UI)
+    const qboolean doFix = qtrue; // no chance of prediction error from UI code
+#endif
+    int *move;
+
+    for (move = WeaponAttackAnim; move - WeaponAttackAnim < ARRAY_LEN(WeaponAttackAnim); move++) {
+        const weapon_e wpIndex = (weapon_e)(move - WeaponAttackAnim);
+        if (wpIndex == WP_CONCUSSION) {
+            *move = doFix ? BOTH_ATTACK3 : BOTH_ATTACK2;
+        } else if (wpIndex == WP_BRYAR_OLD) {
+            *move = doFix ? BOTH_ATTACK2 : BOTH_STAND1;
+        } else if (wpIndex == WP_EMPLACED_GUN) {
+            *move = doFix ? BOTH_STAND1 : BOTH_ATTACK1;
+        } else if (wpIndex == WP_TURRET) {
+            *move = doFix ? BOTH_ATTACK1 : BOTH_ATTACK2; // better than UB?
+        }
+    }
+}
+
 const stringID_table_t eTypes[ET_MAX] = {
     ENUM2STRING(ET_GENERAL),   ENUM2STRING(ET_PLAYER),   ENUM2STRING(ET_ITEM),         ENUM2STRING(ET_MISSILE),
     ENUM2STRING(ET_SPECIAL),   ENUM2STRING(ET_HOLOCRON), ENUM2STRING(ET_MOVER),        ENUM2STRING(ET_BEAM),
diff --git a/game/bg_panimate.cpp b/game/bg_panimate.cpp
index 89f76c5..5e64770 100644
--- a/game/bg_panimate.cpp
+++ b/game/bg_panimate.cpp
@@ -2488,8 +2488,9 @@ void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts, in
         animations = bgAllAnims[0].anims;
     }
 
-    //	if ( !animations )
-    //		return;
+    if (!animations) {
+        return;
+    }
 
     if (animations[anim].firstFrame == 0 && animations[anim].numFrames == 0) {
         if (anim == BOTH_RUNBACK1 || anim == BOTH_WALKBACK1 || anim == BOTH_RUN1) { // hack for droids
diff --git a/game/bg_pmove.cpp b/game/bg_pmove.cpp
index ff59c79..14f0e2f 100644
--- a/game/bg_pmove.cpp
+++ b/game/bg_pmove.cpp
@@ -4700,6 +4700,16 @@ static uint32_t JP_GetJPFixRoll(void) {
     return level;
 }
 
+static bool BG_AreRunWalkAnimsFixed(void) {
+#if defined(PROJECT_GAME)
+    return !!g_fixRunWalkAnims.integer;
+#elif defined(PROJECT_CGAME)
+    const char *cs = CG_ConfigString(CS_LEGACY_FIXES);
+    const uint32_t legacyFixes = strtoul(cs, NULL, 0);
+    return !!(legacyFixes & (1 << LEGACYFIX_RUNWALKANIMS));
+#endif
+}
+
 static void PM_Footsteps(void) {
     float bobmove;
     int old;
@@ -4882,101 +4892,115 @@ static void PM_Footsteps(void) {
                     desiredAnim = BOTH_WALK1;
                 }
             } else if (pm->ps->pm_flags & PMF_BACKWARDS_RUN) {
-                switch (pm->ps->fd.saberAnimLevel) {
-                case SS_STAFF:
-                    if (pm->ps->saberHolstered > 1) { // saber off
-                        desiredAnim = BOTH_RUNBACK1;
-                    } else {
-                        // desiredAnim = BOTH_RUNBACK_STAFF;
-                        // hmm.. stuff runback anim is pretty messed up for some reason.
-                        desiredAnim = BOTH_RUNBACK2;
-                    }
-                    break;
-                case SS_DUAL:
-                    if (pm->ps->saberHolstered > 1) { // sabers off
-                        desiredAnim = BOTH_RUNBACK1;
-                    } else {
-                        // desiredAnim = BOTH_RUNBACK_DUAL;
-                        // and so is the dual
-                        desiredAnim = BOTH_RUNBACK2;
-                    }
-                    break;
-                default:
-                    if (pm->ps->saberHolstered) { // saber off
-                        desiredAnim = BOTH_RUNBACK1;
-                    } else {
-                        desiredAnim = BOTH_RUNBACK2;
+                if (BG_AreRunWalkAnimsFixed() && pm->ps->weapon != WP_SABER) {
+                    desiredAnim = BOTH_RUNBACK1;
+                } else {
+                    switch (pm->ps->fd.saberAnimLevel) {
+                    case SS_STAFF:
+                        if (pm->ps->saberHolstered > 1) { // saber off
+                            desiredAnim = BOTH_RUNBACK1;
+                        } else {
+                            // desiredAnim = BOTH_RUNBACK_STAFF;
+                            // hmm.. stuff runback anim is pretty messed up for some reason.
+                            desiredAnim = BOTH_RUNBACK2;
+                        }
+                        break;
+                    case SS_DUAL:
+                        if (pm->ps->saberHolstered > 1) { // sabers off
+                            desiredAnim = BOTH_RUNBACK1;
+                        } else {
+                            // desiredAnim = BOTH_RUNBACK_DUAL;
+                            // and so is the dual
+                            desiredAnim = BOTH_RUNBACK2;
+                        }
+                        break;
+                    default:
+                        if (pm->ps->saberHolstered) { // saber off
+                            desiredAnim = BOTH_RUNBACK1;
+                        } else {
+                            desiredAnim = BOTH_RUNBACK2;
+                        }
+                        break;
                     }
-                    break;
                 }
             } else {
-                switch (pm->ps->fd.saberAnimLevel) {
-                case SS_STAFF:
-                    if (pm->ps->saberHolstered > 1) { // blades off
-                        desiredAnim = BOTH_RUN1;
-                    } else if (pm->ps->saberHolstered == 1) { // 1 blade on
-                        desiredAnim = BOTH_RUN2;
-                    } else {
-                        if (pm->ps->fd.forcePowersActive & (1 << FP_SPEED)) {
+                if (BG_AreRunWalkAnimsFixed() && pm->ps->weapon != WP_SABER) {
+                    desiredAnim = BOTH_RUN1;
+                } else {
+                    switch (pm->ps->fd.saberAnimLevel) {
+                    case SS_STAFF:
+                        if (pm->ps->saberHolstered > 1) { // blades off
                             desiredAnim = BOTH_RUN1;
+                        } else if (pm->ps->saberHolstered == 1) { // 1 blade on
+                            desiredAnim = BOTH_RUN2;
                         } else {
-                            desiredAnim = BOTH_RUN_STAFF;
+                            if (pm->ps->fd.forcePowersActive & (1 << FP_SPEED)) {
+                                desiredAnim = BOTH_RUN1;
+                            } else {
+                                desiredAnim = BOTH_RUN_STAFF;
+                            }
                         }
+                        break;
+                    case SS_DUAL:
+                        if (pm->ps->saberHolstered > 1) { // blades off
+                            desiredAnim = BOTH_RUN1;
+                        } else if (pm->ps->saberHolstered == 1) { // 1 saber on
+                            desiredAnim = BOTH_RUN2;
+                        } else {
+                            desiredAnim = BOTH_RUN_DUAL;
+                        }
+                        break;
+                    default:
+                        if (pm->ps->saberHolstered) { // saber off
+                            desiredAnim = BOTH_RUN1;
+                        } else {
+                            desiredAnim = BOTH_RUN2;
+                        }
+                        break;
                     }
-                    break;
-                case SS_DUAL:
-                    if (pm->ps->saberHolstered > 1) { // blades off
-                        desiredAnim = BOTH_RUN1;
-                    } else if (pm->ps->saberHolstered == 1) { // 1 saber on
-                        desiredAnim = BOTH_RUN2;
-                    } else {
-                        desiredAnim = BOTH_RUN_DUAL;
-                    }
-                    break;
-                default:
-                    if (pm->ps->saberHolstered) { // saber off
-                        desiredAnim = BOTH_RUN1;
-                    } else {
-                        desiredAnim = BOTH_RUN2;
-                    }
-                    break;
                 }
             }
         } else {
             bobmove = 0.2f; // walking bobs slow
             if (pm->ps->pm_flags & PMF_BACKWARDS_RUN) {
-                switch (pm->ps->fd.saberAnimLevel) {
-                case SS_STAFF:
-                    if (pm->ps->saberHolstered > 1) {
-                        desiredAnim = BOTH_WALKBACK1;
-                    } else if (pm->ps->saberHolstered) {
-                        desiredAnim = BOTH_WALKBACK2;
-                    } else {
-                        desiredAnim = BOTH_WALKBACK_STAFF;
-                    }
-                    break;
-                case SS_DUAL:
-                    if (pm->ps->saberHolstered > 1) {
-                        desiredAnim = BOTH_WALKBACK1;
-                    } else if (pm->ps->saberHolstered) {
-                        desiredAnim = BOTH_WALKBACK2;
-                    } else {
-                        desiredAnim = BOTH_WALKBACK_DUAL;
-                    }
-                    break;
-                default:
-                    if (pm->ps->saberHolstered) {
-                        desiredAnim = BOTH_WALKBACK1;
-                    } else {
-                        desiredAnim = BOTH_WALKBACK2;
+                if (BG_AreRunWalkAnimsFixed() && pm->ps->weapon != WP_SABER) {
+                    desiredAnim = BOTH_WALKBACK1;
+                } else {
+                    switch (pm->ps->fd.saberAnimLevel) {
+                    case SS_STAFF:
+                        if (pm->ps->saberHolstered > 1) {
+                            desiredAnim = BOTH_WALKBACK1;
+                        } else if (pm->ps->saberHolstered) {
+                            desiredAnim = BOTH_WALKBACK2;
+                        } else {
+                            desiredAnim = BOTH_WALKBACK_STAFF;
+                        }
+                        break;
+                    case SS_DUAL:
+                        if (pm->ps->saberHolstered > 1) {
+                            desiredAnim = BOTH_WALKBACK1;
+                        } else if (pm->ps->saberHolstered) {
+                            desiredAnim = BOTH_WALKBACK2;
+                        } else {
+                            desiredAnim = BOTH_WALKBACK_DUAL;
+                        }
+                        break;
+                    default:
+                        if (pm->ps->saberHolstered) {
+                            desiredAnim = BOTH_WALKBACK1;
+                        } else {
+                            desiredAnim = BOTH_WALKBACK2;
+                        }
+                        break;
                     }
-                    break;
                 }
             } else {
                 if (pm->ps->weapon == WP_MELEE) {
                     desiredAnim = BOTH_WALK1;
                 } else if (BG_SabersOff(pm->ps)) {
                     desiredAnim = BOTH_WALK1;
+                } else if (BG_AreRunWalkAnimsFixed() && pm->ps->weapon != WP_SABER) {
+                    desiredAnim = BOTH_WALK1;
                 } else {
                     switch (pm->ps->fd.saberAnimLevel) {
                     case SS_STAFF:
diff --git a/game/bg_public.h b/game/bg_public.h
index 5a7be16..d423e00 100644
--- a/game/bg_public.h
+++ b/game/bg_public.h
@@ -140,6 +140,8 @@ Ghoul2 Insert End
 
 enum legacyFixes_e {
     LEGACYFIX_SABERMOVEDATA = 0,
+    LEGACYFIX_WEAPONATTACKANIM,
+    LEGACYFIX_RUNWALKANIMS,
     /*
     m    m                        ""#      "             m                    m
     #    #  mmm   m   m             #    mmm     mmm   mm#mm   mmm   m mm     #
@@ -1587,7 +1589,7 @@ typedef struct saberInfo_s {
 
 extern const int WeaponReadyAnim[WP_NUM_WEAPONS];
 extern const int WeaponReadyLegsAnim[WP_NUM_WEAPONS];
-extern const int WeaponAttackAnim[WP_NUM_WEAPONS];
+extern int WeaponAttackAnim[WP_NUM_WEAPONS];
 
 extern const int forcePowerDarkLight[NUM_FORCE_POWERS];
 
@@ -1631,6 +1633,7 @@ const gitem_t *BG_FindItemForHoldable(holdable_e hi);
 const gitem_t *BG_FindItemForPowerup(powerup_e pw);
 const gitem_t *BG_FindItemForWeapon(weapon_e wp);
 void BG_FixSaberMoveData(void);
+void BG_FixWeaponAttackAnim(void);
 qboolean BG_FlippingAnim(int anim);
 void BG_ForcePowerDrain(playerState_t *ps, forcePowers_t forcePower, int overrideAmt);
 void BG_G2ATSTAngles(void *ghoul2, int time, vector3 *cent_lerpAngles);
diff --git a/game/bg_saberLoad.cpp b/game/bg_saberLoad.cpp
index 2650a9c..8cc7368 100644
--- a/game/bg_saberLoad.cpp
+++ b/game/bg_saberLoad.cpp
@@ -544,7 +544,7 @@ static void Saber_ParseSaberType(saberInfo_t *saber, const char **p) {
     if (COM_ParseString(p, &value))
         return;
     saberType = GetIDForString(saberTable, value);
-    if (saberType >= SABER_SINGLE && saberType <= NUM_SABERS)
+    if (saberType >= SABER_SINGLE && saberType < NUM_SABERS)
         saber->type = (saberType_e)saberType;
 }
 static void Saber_ParseSaberModel(saberInfo_t *saber, const char **p) {
diff --git a/game/g_admin.cpp b/game/g_admin.cpp
index c90949e..f0be806 100644
--- a/game/g_admin.cpp
+++ b/game/g_admin.cpp
@@ -179,13 +179,13 @@ static void AM_ConsolePrint(const gentity_t *ent, const char *msg) {
     if (ent) {
         trap->SendServerCommand(ent - g_entities, va("print \"%s\"", msg));
     } else {
-        trap->Print(msg);
+        trap->Print("%s", msg);
     }
 }
 
 static void PB_Callback(const char *buffer, int clientNum) {
     if (clientNum == -1) {
-        trap->Print(buffer);
+        trap->Print("%s", buffer);
     } else {
         trap->SendServerCommand(clientNum, va("print \"%s\"", buffer));
     }
diff --git a/game/g_bot.cpp b/game/g_bot.cpp
index b03c410..37ccbda 100644
--- a/game/g_bot.cpp
+++ b/game/g_bot.cpp
@@ -3,6 +3,7 @@
 // g_bot.c
 
 #include "g_local.h"
+#include "g_public.h"
 
 #define BOT_BEGIN_DELAY_BASE 2000
 #define BOT_BEGIN_DELAY_INCREMENT 1500
diff --git a/game/g_client.cpp b/game/g_client.cpp
index b61470c..d05a863 100644
--- a/game/g_client.cpp
+++ b/game/g_client.cpp
@@ -1863,8 +1863,8 @@ qboolean ClientUserinfoChanged(int clientNum) {
         } else if (client->pers.adminData.renamedTime != 0 && client->pers.adminData.renamedTime > level.time - (japp_amrenameTime.value * 60.0f) * 1000) {
             float remaining = japp_amrenameTime.value * 60.0f;
             remaining -= (level.time - client->pers.adminData.renamedTime) / 1000.0f;
-            trap->SendServerCommand(clientNum,
-                                    va("print \"You are not allowed to change name for another " S_COLOR_CYAN "%.1f " S_COLOR_WHITE "seconds\n\"", (double)remaining));
+            trap->SendServerCommand(
+                clientNum, va("print \"You are not allowed to change name for another " S_COLOR_CYAN "%.1f " S_COLOR_WHITE "seconds\n\"", (double)remaining));
         } else {
             trap->SendServerCommand(-1,
                                     va("print \"%s" S_COLOR_WHITE " %s %s\n\"", oldname, G_GetStringEdString("MP_SVGAME", "PLRENAME"), client->pers.netname));
@@ -2178,19 +2178,17 @@ const char *ClientConnect(int clientNum, qboolean firstTime, qboolean isBot) {
     }
 
     // disallow multiple connections from same IP
-    if (!isBot && firstTime) {
-        if (japp_antiFakePlayer.integer) {
-            // check for > g_maxConnPerIP connections from same IP
-            int count = 0, i = 0;
-            for (i = 0; i < sv_maxclients.integer; i++) {
-                if (CompareIPString(tmpIP, level.clients[i].sess.IP)) {
-                    count++;
-                }
-            }
-            if (count > japp_maxConnPerIP.integer) {
-                return "Too many connections from the same IP";
+    if (japp_antiFakePlayer.integer && !isBot && firstTime) {
+        int count = 0, i = 0;
+        gclient_t *cl;
+        for (i = 0, cl = level.clients; i < sv_maxclients.integer; i++, cl++) {
+            if (cl->pers.connected >= CON_CONNECTING && CompareIPString(tmpIP, cl->sess.IP)) {
+                count++;
             }
         }
+        if (count >= japp_maxConnPerIP.integer) {
+            return "Too many connections from the same IP";
+        }
     }
 
     if (ent->inuse) {
@@ -2236,7 +2234,7 @@ const char *ClientConnect(int clientNum, qboolean firstTime, qboolean isBot) {
         }
         Q_strcat(msg, sizeof(msg), va(" final csf 0x%X\n", finalCSF));
 
-        G_LogPrintf(level.log.console, msg);
+        G_LogPrintf(level.log.console, "%s", msg);
     }
 
     // JPLua plugins can deny connections
diff --git a/game/g_cmds.cpp b/game/g_cmds.cpp
index 404c5cc..231212b 100644
--- a/game/g_cmds.cpp
+++ b/game/g_cmds.cpp
@@ -1,5 +1,6 @@
 // Copyright (C) 1999-2000 Id Software, Inc.
 //
+#include "anims.h"
 #include "g_local.h"
 #include "bg_saga.h"
 #include "g_admin.h"
@@ -3164,6 +3165,7 @@ static const emote_t emotes[] = {
     {"victory", BOTH_TAVION_SCEPTERGROUND, MAX_ANIMATIONS, EMF_NONE},
     {"wait", BOTH_STAND10, BOTH_STAND10TOSTAND1, EMF_STATIC | EMF_HOLD | EMF_HOLSTER},
     {"won", TORSO_HANDSIGNAL1, MAX_ANIMATIONS, EMF_NONE},
+    {"worm", BOTH_WORM, MAX_ANIMATIONS, EMF_STATIC | EMF_HOLD},
 };
 static const size_t numEmotes = ARRAY_LEN(emotes);
 
@@ -3277,6 +3279,7 @@ EMOTE(victory)
 EMOTE(surrender)
 EMOTE(wait)
 EMOTE(won)
+EMOTE(worm)
 
 static void Cmd_KnockMeDown(gentity_t *ent) { G_Knockdown(ent); }
 
@@ -3438,6 +3441,7 @@ static const command_t commands[] = {
     {"amvictory", Cmd_Emote_victory, GTB_ALL, CMDFLAG_NOINTERMISSION | CMDFLAG_ALIVE},
     {"amwait", Cmd_Emote_wait, GTB_ALL, CMDFLAG_NOINTERMISSION | CMDFLAG_ALIVE},
     {"amwon", Cmd_Emote_won, GTB_ALL, CMDFLAG_NOINTERMISSION | CMDFLAG_ALIVE},
+    {"amworm", Cmd_Emote_worm, GTB_ALL, CMDFLAG_NOINTERMISSION | CMDFLAG_ALIVE},
     {"callvote", Cmd_CallVote_f, GTB_ALL, CMDFLAG_NOINTERMISSION},
     {"debugBMove_Back", Cmd_BotMoveBack_f, GTB_ALL, CMDFLAG_CHEAT | CMDFLAG_ALIVE},
     {"debugBMove_Forward", Cmd_BotMoveForward_f, GTB_ALL, CMDFLAG_CHEAT | CMDFLAG_ALIVE},
diff --git a/game/g_main.cpp b/game/g_main.cpp
index 1555a05..1a9412e 100644
--- a/game/g_main.cpp
+++ b/game/g_main.cpp
@@ -170,45 +170,51 @@ static void CVU_CInfo(void) { CPM_UpdateSettings(!!(jp_cinfo.bits & CINFO_CPMPHY
 
 static void CVU_Motd(void) { Q_ConvertLinefeeds(g_motd.string); }
 
-static void CVU_FixSaberMoveData(void) {
+static void UpdateLegacyFixesConfigstring(legacyFixes_e legacyFix, qboolean enabled) {
     char sLegacyFixes[32];
     trap->GetConfigstring(CS_LEGACY_FIXES, sLegacyFixes, sizeof(sLegacyFixes));
 
     uint32_t legacyFixes = strtoul(sLegacyFixes, NULL, 0);
-    if (g_fixSaberMoveData.integer) {
-        legacyFixes |= (1 << LEGACYFIX_SABERMOVEDATA);
+    if (enabled) {
+        legacyFixes |= (1 << legacyFix);
     } else {
-        legacyFixes &= ~(1 << LEGACYFIX_SABERMOVEDATA);
+        legacyFixes &= ~(1 << legacyFix);
     }
     trap->SetConfigstring(CS_LEGACY_FIXES, va("%" PRIu32, legacyFixes));
 }
 
-typedef struct cvarTable_s {
-    vmCvar_t *vmCvar;
-    const char *cvarName, *defaultString;
-    void (*update)(void);
-    uint32_t cvarFlags;
-    qboolean trackChange; // track this variable, and announce if changed
-} cvarTable_t;
+static void CVU_FixSaberMoveData(void) {
+    BG_FixSaberMoveData();
+    UpdateLegacyFixesConfigstring(LEGACYFIX_SABERMOVEDATA, g_fixSaberMoveData.integer);
+}
+
+static void CVU_FixRunWalkAnims(void) { UpdateLegacyFixesConfigstring(LEGACYFIX_RUNWALKANIMS, g_fixRunWalkAnims.integer); }
+
+static void CVU_FixWeaponAttackAnim(void) {
+    BG_FixWeaponAttackAnim();
+    UpdateLegacyFixesConfigstring(LEGACYFIX_WEAPONATTACKANIM, g_fixWeaponAttackAnim.integer);
+}
 
 #define XCVAR_DECL
 #include "g_xcvar.h"
 #undef XCVAR_DECL
 
-static cvarTable_t gameCvarTable[] = {
+static const struct cvarTable_t {
+    vmCvar_t *vmCvar;
+    const char *cvarName, *defaultString;
+    void (*update)(void);
+    uint32_t cvarFlags;
+    qboolean trackChange; // track this variable, and announce if changed
+} cvarTable[] = {
 #define XCVAR_LIST
 #include "g_xcvar.h"
 #undef XCVAR_LIST
 };
-static int gameCvarTableSize = ARRAY_LEN(gameCvarTable);
 
 const char *G_Cvar_DefaultString(const vmCvar_t *vmCvar) {
-    int i = 0;
-    const cvarTable_t *cv = NULL;
-
-    for (i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++) {
-        if (cv->vmCvar == vmCvar) {
-            return cv->defaultString;
+    for (const auto &cv : cvarTable) {
+        if (cv.vmCvar == vmCvar) {
+            return cv.defaultString;
         }
     }
 
@@ -216,17 +222,15 @@ const char *G_Cvar_DefaultString(const vmCvar_t *vmCvar) {
 }
 
 void G_RegisterCvars(void) {
-    int i = 0;
-    cvarTable_t *cv = NULL;
-
-    // register all cvars
-    for (i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++)
-        trap->Cvar_Register(cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags);
+    for (const auto &cv : cvarTable) {
+        trap->Cvar_Register(cv.vmCvar, cv.cvarName, cv.defaultString, cv.cvarFlags);
+    }
 
     // now update them
-    for (i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++) {
-        if (cv->update)
-            cv->update();
+    for (const auto &cv : cvarTable) {
+        if (cv.update) {
+            cv.update();
+        }
     }
 }
 
@@ -249,22 +253,19 @@ void G_CacheGametype(void) {
 }
 
 void G_UpdateCvars(void) {
-    int i = 0;
-    cvarTable_t *cv = NULL;
-
-    for (i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++) {
-        if (cv->vmCvar) {
-            int modCount = cv->vmCvar->modificationCount;
-            trap->Cvar_Update(cv->vmCvar);
-            if (cv->vmCvar->modificationCount != modCount) {
-                if (cv->update) {
-                    cv->update();
+    for (const auto &cv : cvarTable) {
+        if (cv.vmCvar) {
+            int modCount = cv.vmCvar->modificationCount;
+            trap->Cvar_Update(cv.vmCvar);
+            if (cv.vmCvar->modificationCount != modCount) {
+                if (cv.update) {
+                    cv.update();
                 }
 
-                JPLua::Cvar_Update(cv->cvarName);
+                JPLua::Cvar_Update(cv.cvarName);
 
-                if (cv->trackChange) {
-                    trap->SendServerCommand(-1, va("print \"Server: %s changed to %s\n\"", cv->cvarName, cv->vmCvar->string));
+                if (cv.trackChange) {
+                    trap->SendServerCommand(-1, va("print \"Server: %s changed to %s\n\"", cv.cvarName, cv.vmCvar->string));
                 }
             }
         }
diff --git a/game/g_smartentities.cpp b/game/g_smartentities.cpp
index 57a72c0..3a873e4 100644
--- a/game/g_smartentities.cpp
+++ b/game/g_smartentities.cpp
@@ -234,7 +234,7 @@ static qboolean SE_NetworkPlayer(const gentity_t *self, const gentity_t *other)
 
 // Tracing non-players seems to have a bad effect, we know players are limited to 32 per frame, however other gentities
 //	that are being added are not! It's stupid to actually add traces for it, even with a limited form i used before of 2
-//	traces per object. There are to many too track and simply networking them takes less FPS either way
+//	traces per object. There are too many to track and simply networking them takes less FPS either way
 qboolean G_EntityOccluded(const gentity_t *self, const gentity_t *other) {
     // This is a non-player object, just send it (see above).
     if (!other->inuse || other->s.number >= level.maxclients) {
diff --git a/game/g_utils.cpp b/game/g_utils.cpp
index 41bfb45..39cb668 100644
--- a/game/g_utils.cpp
+++ b/game/g_utils.cpp
@@ -1633,7 +1633,7 @@ int G_ClientFromString(const gentity_t *ent, const char *match, uint32_t flags)
             if (ent) {
                 trap->SendServerCommand(ent - g_entities, va("print \"%s\"", msg));
             } else {
-                trap->Print(va("print %s", msg));
+                trap->Print("print %s", msg);
             }
             return -1;
         }
@@ -1644,7 +1644,7 @@ int G_ClientFromString(const gentity_t *ent, const char *match, uint32_t flags)
         if (ent) {
             trap->SendServerCommand(ent - g_entities, va("print \"Client %s does not exist\n\"", cleanedMatch));
         } else {
-            trap->Print(va("Client %s does not exist\n", cleanedMatch));
+            trap->Print("Client %s does not exist\n", cleanedMatch);
         }
     }
     return -1;
diff --git a/game/g_xcvar.h b/game/g_xcvar.h
index ba8cdd1..162b605 100644
--- a/game/g_xcvar.h
+++ b/game/g_xcvar.h
@@ -67,6 +67,8 @@ XCVAR_DEF(g_duelWeaponDisable, "1", NULL, CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_
 XCVAR_DEF(g_ff_objectives, "0", NULL, CVAR_CHEAT | CVAR_NORESTART, qtrue)
 XCVAR_DEF(g_fixSaberDisarmBonus, "1", NULL, CVAR_ARCHIVE, qtrue)
 XCVAR_DEF(g_fixSaberMoveData, "1", CVU_FixSaberMoveData, CVAR_ARCHIVE, qtrue)
+XCVAR_DEF(g_fixRunWalkAnims, "1", CVU_FixRunWalkAnims, CVAR_ARCHIVE, qtrue)
+XCVAR_DEF(g_fixWeaponAttackAnim, "1", CVU_FixWeaponAttackAnim, CVAR_ARCHIVE, qtrue)
 XCVAR_DEF(g_forceBasedTeams, "0", NULL, CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_LATCH, qfalse)
 XCVAR_DEF(g_forceClientUpdateRate, "250", NULL, CVAR_NONE, qfalse)
 XCVAR_DEF(g_forceDodge, "1", NULL, CVAR_NONE, qtrue)
diff --git a/qcommon/q_shared.h b/qcommon/q_shared.h
index a500db4..79fffc1 100644
--- a/qcommon/q_shared.h
+++ b/qcommon/q_shared.h
@@ -155,8 +155,8 @@ typedef int32_t qhandle_t, fxHandle_t, sfxHandle_t, fileHandle_t, clipHandle_t;
 
 // ja++ version
 #define JAPP_VERSION_SMALL "JA++, " XSTRING(ARCH_WIDTH) " bits, " __DATE__
-#ifdef REVISION
-#define JAPP_VERSION JAPP_VERSION_SMALL ", " REVISION
+#ifdef GIT_TAG
+#define JAPP_VERSION JAPP_VERSION_SMALL ", " GIT_TAG
 #else
 #define JAPP_VERSION JAPP_VERSION_SMALL
 #endif
@@ -765,7 +765,7 @@ extern vector3 axisDefault[3];
 float Q_fabs(float f);
 float Q_rsqrt(float f); // reciprocal square root
 
-#define SQRTFAST(x) ((x) * Q_rsqrt(x))
+#define SQRTFAST(x) ((x)*Q_rsqrt(x))
 
 signed char ClampChar(int i);
 signed short ClampShort(int i);
@@ -782,8 +782,8 @@ void ByteToDir(int b, vector3 *dir);
 
 #define Square(x) ((x) * (x))
 
-#define DEG2RAD(a) (((a) * M_PI) / 180.0F)
-#define RAD2DEG(a) (((a) * 180.0f) / M_PI)
+#define DEG2RAD(a) (((a)*M_PI) / 180.0F)
+#define RAD2DEG(a) (((a)*180.0f) / M_PI)
 
 void VectorAdd(const vector3 *vec1, const vector3 *vec2, vector3 *vecOut);
 void VectorSubtract(const vector3 *vec1, const vector3 *vec2, vector3 *vecOut);
@@ -1154,7 +1154,7 @@ typedef enum soundChannel_e {
     CHAN_MUSIC,        // ## %s !!"W:\game\base\!!sound\*.wav;*.mp3" #music played as a looping sound - added by BTO (VV)
 } soundChannel_t;
 
-#define ANGLE2SHORT(x) ((int)((x) * 65536 / 360) & 65535)
+#define ANGLE2SHORT(x) ((int)((x)*65536 / 360) & 65535)
 #define SHORT2ANGLE(x) ((x) * (360.0f / 65536))
 
 #define SNAPFLAG_RATE_DELAYED 1
diff --git a/ui/ui_cvar.cpp b/ui/ui_cvar.cpp
index d8953b3..14845d7 100644
--- a/ui/ui_cvar.cpp
+++ b/ui/ui_cvar.cpp
@@ -10,13 +10,6 @@ static void CVU_Derpity( void ) {
 
 // Cvar table
 
-typedef struct cvarTable_s {
-    vmCvar_t *vmCvar;
-    const char *cvarName, *defaultString;
-    void (*update)(void);
-    uint32_t cvarFlags;
-} cvarTable_t;
-
 #define XCVAR_DECL
 #include "ui_xcvar.h"
 #undef XCVAR_DECL
@@ -35,17 +28,18 @@ static void CVU_Master3(void) { trap->Cmd_ExecuteText(EXEC_NOW, va("set sv_maste
 static void CVU_Master4(void) { trap->Cmd_ExecuteText(EXEC_NOW, va("set sv_master4 \"%s\"", ui_sv_master4.string)); }
 static void CVU_Master5(void) { trap->Cmd_ExecuteText(EXEC_NOW, va("set sv_master5 \"%s\"", ui_sv_master5.string)); }
 
-static const cvarTable_t uiCvarTable[] = {
+static const struct cvarTable_t {
+    vmCvar_t *vmCvar;
+    const char *cvarName, *defaultString;
+    void (*update)(void);
+    uint32_t cvarFlags;
+} cvarTable[] = {
 #define XCVAR_LIST
 #include "ui_xcvar.h"
 #undef XCVAR_LIST
 };
-static const size_t uiCvarTableSize = ARRAY_LEN(uiCvarTable);
 
 void UI_RegisterCvars(void) {
-    size_t i = 0;
-    const cvarTable_t *cv = NULL;
-
     char buf[MAX_CVAR_VALUE_STRING];
     trap->Cvar_VariableStringBuffer("sv_master1", buf, sizeof(buf));
     trap->Cvar_Set("ui_sv_master1", buf);
@@ -58,24 +52,25 @@ void UI_RegisterCvars(void) {
     trap->Cvar_VariableStringBuffer("sv_master5", buf, sizeof(buf));
     trap->Cvar_Set("ui_sv_master5", buf);
 
-    for (i = 0, cv = uiCvarTable; i < uiCvarTableSize; i++, cv++) {
-        trap->Cvar_Register(cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags);
-        if (cv->update)
-            cv->update();
+    for (const auto &cv : cvarTable) {
+        trap->Cvar_Register(cv.vmCvar, cv.cvarName, cv.defaultString, cv.cvarFlags);
+    }
+    for (const auto &cv : cvarTable) {
+        if (cv.update) {
+            cv.update();
+        }
     }
 }
 
 void UI_UpdateCvars(void) {
-    size_t i = 0;
-    const cvarTable_t *cv = NULL;
-
-    for (i = 0, cv = uiCvarTable; i < uiCvarTableSize; i++, cv++) {
-        if (cv->vmCvar) {
-            int modCount = cv->vmCvar->modificationCount;
-            trap->Cvar_Update(cv->vmCvar);
-            if (cv->vmCvar->modificationCount != modCount) {
-                if (cv->update)
-                    cv->update();
+    for (const auto &cv : cvarTable) {
+        if (cv.vmCvar) {
+            int modCount = cv.vmCvar->modificationCount;
+            trap->Cvar_Update(cv.vmCvar);
+            if (cv.vmCvar->modificationCount != modCount) {
+                if (cv.update) {
+                    cv.update();
+                }
             }
         }
     }
diff --git a/ui/ui_main.cpp b/ui/ui_main.cpp
index 623461d..241d6c3 100644
--- a/ui/ui_main.cpp
+++ b/ui/ui_main.cpp
@@ -8327,6 +8327,25 @@ void JP_SaveFavServers(void) {
 }
 #endif
 
+static void UI_CvarHelp(const char *cvarName, qboolean enter, char *helpBuffer, size_t helpBufferSize) {
+    /*
+    In the digital realm, where lines of code dance and weave,
+    One missing export can bring a program to its knees.
+    A fragile balance disrupted by a single slip,
+    Don't crash my program, let it continue its script.
+
+    Each function, each module, carefully crafted and linked,
+    A symphony of logic, with no room for a missing brink.
+    So delicate, so intricate, a web of commands,
+    Don't let it break, don't let it end.
+
+    For in this world of bytes and bits, where errors hide,
+    A missing export can be a fatal divide.
+    So heed my plea, oh code gods above,
+    Don't crash my program, let it run with love.
+    */
+}
+
 uiImport_t *trap = NULL;
 
 Q_CABI {
@@ -8355,6 +8374,7 @@ Q_CABI {
         uie.ConsoleCommand = UI_ConsoleCommand;
         uie.DrawConnectScreen = UI_DrawConnectScreen;
         uie.MenuReset = Menu_Reset;
+        uie.CvarHelp = UI_CvarHelp;
 
         return &uie;
     }
diff --git a/ui/ui_public.h b/ui/ui_public.h
index d444a9c..e22801e 100644
--- a/ui/ui_public.h
+++ b/ui/ui_public.h
@@ -3,6 +3,9 @@
 // Copyright (C) 1999-2000 Id Software, Inc.
 //
 
+#include "qcommon/q_shared.h"
+#include "cgame/tr_types.h"
+
 #define UI_API_VERSION 3
 #define UI_LEGACY_API_VERSION 7
 
@@ -208,274 +211,144 @@ enum uiExportLegacy_e {
 
 typedef struct uiImport_s {
     void (*Print)(const char *msg, ...);
-
     void (*Error)(int level, const char *error, ...);
-
     int (*Milliseconds)(void);
-
     int (*RealTime)(qtime_t *qtime);
-
     int (*MemoryRemaining)(void);
-
     void (*Cvar_Create)(const char *var_name, const char *var_value, uint32_t flags);
-
     void (*Cvar_InfoStringBuffer)(int bit, char *buffer, int bufsize);
-
     void (*Cvar_Register)(vmCvar_t *cvar, const char *var_name, const char *value, uint32_t flags);
-
     void (*Cvar_Reset)(const char *name);
-
     void (*Cvar_Set)(const char *var_name, const char *value);
-
     void (*Cvar_SetValue)(const char *var_name, float value);
-
     void (*Cvar_Update)(vmCvar_t *cvar);
-
     void (*Cvar_VariableStringBuffer)(const char *var_name, char *buffer, int bufsize);
-
     float (*Cvar_VariableValue)(const char *var_name);
-
     int (*Cmd_Argc)(void);
-
     void (*Cmd_Argv)(int n, char *buffer, int bufferLength);
-
     void (*Cmd_ExecuteText)(int exec_when, const char *text);
-
     void (*FS_Close)(fileHandle_t f);
-
     int (*FS_GetFileList)(const char *path, const char *extension, char *listbuf, int bufsize);
-
     int (*FS_Open)(const char *qpath, fileHandle_t *f, fsMode_t mode);
-
     int (*FS_Read)(void *buffer, int len, fileHandle_t f);
-
     int (*FS_Write)(const void *buffer, int len, fileHandle_t f);
-
     void (*GetClientState)(uiClientState_t *state);
-
     void (*GetClipboardData)(char *buf, int bufsize);
-
     int (*GetConfigString)(int index, char *buff, int buffsize);
-
     void (*GetGlconfig)(glconfig_t *glconfig);
-
     void (*UpdateScreen)(void);
-
     void (*Key_ClearStates)(void);
-
     void (*Key_GetBindingBuf)(int keynum, char *buf, int buflen);
-
     qboolean (*Key_IsDown)(int keynum);
-
     void (*Key_KeynumToStringBuf)(int keynum, char *buf, int buflen);
-
     void (*Key_SetBinding)(int keynum, const char *binding);
-
     int (*Key_GetCatcher)(void);
-
     qboolean (*Key_GetOverstrikeMode)(void);
-
     void (*Key_SetCatcher)(int catcher);
-
     void (*Key_SetOverstrikeMode)(qboolean state);
-
     int (*PC_AddGlobalDefine)(char *define);
-
     int (*PC_FreeSource)(int handle);
-
     int (*PC_LoadGlobalDefines)(const char *filename);
-
     int (*PC_LoadSource)(const char *filename);
-
     int (*PC_ReadToken)(int handle, pc_token_t *pc_token);
-
     void (*PC_RemoveAllGlobalDefines)(void);
-
     int (*PC_SourceFileAndLine)(int handle, char *filename, int *line);
-
     void (*CIN_DrawCinematic)(int handle);
-
     int (*CIN_PlayCinematic)(const char *arg0, int xpos, int ypos, int width, int height, uint32_t bits);
-
     cinState_t (*CIN_RunCinematic)(int handle);
-
     void (*CIN_SetExtents)(int handle, int x, int y, int w, int h);
-
     cinState_t (*CIN_StopCinematic)(int handle);
-
     int (*LAN_AddServer)(int source, const char *name, const char *addr);
-
     void (*LAN_ClearPing)(int n);
-
     int (*LAN_CompareServers)(int source, int sortKey, int sortDir, int s1, int s2);
-
     void (*LAN_GetPing)(int n, char *buf, int buflen, int *pingtime);
-
     void (*LAN_GetPingInfo)(int n, char *buf, int buflen);
-
     int (*LAN_GetPingQueueCount)(void);
-
     void (*LAN_GetServerAddressString)(int source, int n, char *buf, int buflen);
-
     int (*LAN_GetServerCount)(int source);
-
     void (*LAN_GetServerInfo)(int source, int n, char *buf, int buflen);
-
     int (*LAN_GetServerPing)(int source, int n);
-
     void (*LAN_LoadCachedServers)(void);
-
     void (*LAN_MarkServerVisible)(int source, int n, qboolean visible);
-
     void (*LAN_RemoveServer)(int source, const char *addr);
-
     void (*LAN_ResetPings)(int n);
-
     void (*LAN_SaveCachedServers)(void);
-
     int (*LAN_ServerIsVisible)(int source, int n);
-
     int (*LAN_ServerStatus)(const char *serverAddress, char *serverStatus, int maxLen);
-
     qboolean (*LAN_UpdateVisiblePings)(int source);
-
     void (*S_StartBackgroundTrack)(const char *intro, const char *loop, qboolean bReturnWithoutStarting);
-
     void (*S_StartLocalSound)(sfxHandle_t sfx, int channelNum);
-
     void (*S_StopBackgroundTrack)(void);
-
     sfxHandle_t (*S_RegisterSound)(const char *sample);
-
     void (*SE_GetLanguageName)(const int languageIndex, char *buffer);
-
     int (*SE_GetNumLanguages)(void);
-
     qboolean (*SE_GetStringTextString)(const char *text, char *buffer, int bufferLength);
-
     qboolean (*R_Language_IsAsian)(void);
-
     qboolean (*R_Language_UsesSpaces)(void);
-
     unsigned int (*R_AnyLanguage_ReadCharFromString)(const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation);
-
     void (*R_AddLightToScene)(const vector3 *org, float intensity, float r, float g, float b);
-
     void (*R_AddPolysToScene)(qhandle_t hShader, int numVerts, const polyVert_t *verts, int num);
-
     void (*R_AddRefEntityToScene)(const refEntity_t *re);
-
     void (*R_ClearScene)(void);
-
     void (*R_DrawStretchPic)(float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader);
-
     int (*R_Font_StrLenPixels)(const char *text, const int iFontIndex, const float scale);
-
     int (*R_Font_StrLenChars)(const char *text);
-
     int (*R_Font_HeightPixels)(const int iFontIndex, const float scale);
-
     void (*R_Font_DrawString)(int ox, int oy, const char *text, const vector4 *rgba, const int setIndex, int iCharLimit, const float scale);
-
     int (*R_LerpTag)(orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName);
-
     void (*R_ModelBounds)(clipHandle_t model, vector3 *mins, vector3 *maxs);
-
     qhandle_t (*R_RegisterModel)(const char *name);
-
     qhandle_t (*R_RegisterSkin)(const char *name);
-
     qhandle_t (*R_RegisterShaderNoMip)(const char *name);
-
     qhandle_t (*R_RegisterFont)(const char *fontName);
-
     void (*R_RemapShader)(const char *oldShader, const char *newShader, const char *timeOffset);
-
     void (*R_RenderScene)(const refdef_t *fd);
-
     void (*R_SetColor)(const vector4 *rgba);
-
     void (*R_ShaderNameFromIndex)(char *name, int index);
-
     void (*G2_ListModelSurfaces)(void *ghlInfo);
-
     void (*G2_ListModelBones)(void *ghlInfo, int frame);
-
     void (*G2_SetGhoul2ModelIndexes)(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList);
-
     qboolean (*G2_HaveWeGhoul2Models)(void *ghoul2);
-
     qboolean (*G2API_GetBoltMatrix)(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vector3 *angles, const vector3 *position,
                                     const int frameNum, qhandle_t *modelList, vector3 *scale);
-
     qboolean (*G2API_GetBoltMatrix_NoReconstruct)(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vector3 *angles,
                                                   const vector3 *position, const int frameNum, qhandle_t *modelList, vector3 *scale);
-
     qboolean (*G2API_GetBoltMatrix_NoRecNoRot)(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vector3 *angles,
                                                const vector3 *position, const int frameNum, qhandle_t *modelList, vector3 *scale);
-
     int (*G2API_InitGhoul2Model)(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, qhandle_t customShader, uint32_t modelFlags,
                                  int lodBias);
-
     void (*G2API_CollisionDetect)(CollisionRecord_t *collRecMap, void *ghoul2, const vector3 *angles, const vector3 *position, int frameNumber, int entNum,
                                   vector3 *rayStart, vector3 *rayEnd, vector3 *scale, uint32_t traceFlags, int useLod, float fRadius);
-
     void (*G2API_CollisionDetectCache)(CollisionRecord_t *collRecMap, void *ghoul2, const vector3 *angles, const vector3 *position, int frameNumber, int entNum,
                                        vector3 *rayStart, vector3 *rayEnd, vector3 *scale, uint32_t traceFlags, int useLod, float fRadius);
-
     void (*G2API_CleanGhoul2Models)(void **ghoul2Ptr);
-
     qboolean (*G2API_SetBoneAngles)(void *ghoul2, int modelIndex, const char *boneName, const vector3 *angles, const uint32_t flags, const int up,
                                     const int right, const int forward, qhandle_t *modelList, int blendTime, int currentTime);
-
     qboolean (*G2API_SetBoneAnim)(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, const uint32_t flags,
                                   const float animSpeed, const int currentTime, const float setFrame, const int blendTime);
-
     qboolean (*G2API_GetBoneAnim)(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *startFrame, int *endFrame,
                                   uint32_t *flags, float *animSpeed, int *modelList, const int modelIndex);
-
     qboolean (*G2API_GetBoneFrame)(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex);
-
     void (*G2API_GetGLAName)(void *ghoul2, int modelIndex, char *fillBuf);
-
     int (*G2API_CopyGhoul2Instance)(void *g2From, void *g2To, int modelIndex);
-
     void (*G2API_CopySpecificGhoul2Model)(void *g2From, int modelFrom, void *g2To, int modelTo);
-
     void (*G2API_DuplicateGhoul2Instance)(void *g2From, void **g2To);
-
     qboolean (*G2API_HasGhoul2ModelOnIndex)(void *ghlInfo, int modelIndex);
-
     qboolean (*G2API_RemoveGhoul2Model)(void *ghlInfo, int modelIndex);
-
     int (*G2API_AddBolt)(void *ghoul2, int modelIndex, const char *boneName);
-
     void (*G2API_SetBoltInfo)(void *ghoul2, int modelIndex, int boltInfo);
-
     qboolean (*G2API_SetRootSurface)(void *ghoul2, const int modelIndex, const char *surfaceName);
-
     qboolean (*G2API_SetSurfaceOnOff)(void *ghoul2, const char *surfaceName, const uint32_t flags);
-
     qboolean (*G2API_SetNewOrigin)(void *ghoul2, const int boltIndex);
-
     int (*G2API_GetTime)(void);
-
     void (*G2API_SetTime)(int time, int clock);
-
     void (*G2API_SetRagDoll)(void *ghoul2, sharedRagDollParams_t *params);
-
     void (*G2API_AnimateG2Models)(void *ghoul2, int time, sharedRagDollUpdateParams_t *params);
-
     qboolean (*G2API_SetBoneIKState)(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params);
-
     qboolean (*G2API_IKMove)(void *ghoul2, int time, sharedIKMoveParams_t *params);
-
     void (*G2API_GetSurfaceName)(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf);
-
     qboolean (*G2API_SetSkin)(void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin);
-
     qboolean (*G2API_AttachG2Model)(void *ghoul2From, int modelIndexFrom, void *ghoul2To, int toBoltIndex, int toModel);
-
     struct {
         float (*R_Font_StrLenPixels)(const char *text, const int iFontIndex, const float scale);
         void (*AddCommand)(const char *cmd_name);
@@ -485,22 +358,14 @@ typedef struct uiImport_s {
 
 typedef struct uiExport_s {
     void (*Init)(qboolean inGameLoad);
-
     void (*Shutdown)(void);
-
     void (*KeyEvent)(int key, qboolean down);
-
     void (*MouseEvent)(int dx, int dy);
-
     void (*Refresh)(int realtime);
-
     qboolean (*IsFullscreen)(void);
-
     void (*SetActiveMenu)(uiMenuCommand_e menu);
-
     qboolean (*ConsoleCommand)(int realTime);
-
     void (*DrawConnectScreen)(qboolean overlay);
-
     void (*MenuReset)(void);
+    void (*CvarHelp)(const char *cvarName, qboolean enter, char *helpBuffer, size_t helpBufferSize);
 } uiExport_t;
diff --git a/ui/ui_shared.cpp b/ui/ui_shared.cpp
index ee8089d..f5f0bd5 100644
--- a/ui/ui_shared.cpp
+++ b/ui/ui_shared.cpp
@@ -424,8 +424,8 @@ qboolean String_Parse(char **p, const char **out) {
 qboolean PC_String_Parse(int handle, const char **out) {
     static const char *squiggy = "}";
     pc_token_t token{};
-    static int counter = 0;
-    counter++;
+    // static int counter = 0;
+    // counter++;
 
     if (!trap->PC_ReadToken(handle, &token))
         return qfalse;
@@ -6712,7 +6712,7 @@ qboolean ItemParse_flag(itemDef_t *item, int handle) {
     }
 
     if (itemFlags[i].string == NULL) {
-        Com_Printf( S_COLOR_YELLOW "Unknown item style value '%s'", token.string);
+        Com_Printf(S_COLOR_YELLOW "Unknown item style value '%s'", token.string);
     }
 
     return qtrue;