From 09528c294422cbf3e03f06e1b6a2662bf8943018 Mon Sep 17 00:00:00 2001 From: Marco Hladik Date: Thu, 20 May 2021 16:01:07 +0200 Subject: [PATCH] Add support for top/bottom color on players and viewmodels. Fix func_breakable's sound shader from precaching non existing sounds. --- README.md | 39 ++ base/src/shared/player.qc | 2 +- .../base_glsl.pk3dir/glsl/defaultskin.glsl | 410 +++++++++--------- .../sound/func_breakable.sndshd | 2 - src/client/view.qc | 1 + src/menu-fn/m_customize.qc | 104 ++++- src/shared/player.h | 1 + src/shared/player.qc | 15 +- 8 files changed, 362 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index d09bbfac..6968428f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,45 @@ If the compiler isn't found it will ask you to build them. For best results, symlink the desired scripts into your home directory's bin folder. The scripts are designed to be aware of their actual location in the filesystem. +## Dependencies + +Rough list of dependencies for the average Linux system. +On some systems the names may slightly differ. These should be correct for OpenSUSE. + +### Engine +* subversion +* gmake +* gcc +* mesa-libGL-devel +* libgnutls-devel +* libopenal-devel +* libX11-devel +* libXcursor-devel +* libXrandr-devel +* libSDL2-devel (only if you pass BUILD_SDL2=1 to build_engine.sh) + +For WinNT and Darwin systems it's recommended you use the SDL2 backend, but native backends +may be available in FTEQW. + +The X development packages are obviously only a requirement for when you do not compile an SDL2 build on a UNIX-like system. + +### FFMPEG Plugin +* ffmpeg-4-libavformat-devel +* ffmpeg-4-libswscale-devel + +You want this plugin if you want playback of a variety of media formats, including video decoding. + +### Worldspawn Level Editor +* cmake +* gcc-c++ +* gtk2-devel +* gtkglext-devel +* libxml2-devel +* libjpeg8-devel +* minizip-devel + +This is the only component that requires a C++ compiler. + ## Support Join us on irc.vera-visions.com and chat if you're interested in using this in production. **All this is provided to you for free as-is otherwise.** diff --git a/base/src/shared/player.qc b/base/src/shared/player.qc index 95f4230f..1979e33f 100644 --- a/base/src/shared/player.qc +++ b/base/src/shared/player.qc @@ -23,7 +23,7 @@ enumflags PLAYER_ORIGIN_Z, PLAYER_ANGLES_X, PLAYER_ANGLES_Y, - PLAYER_ANGLES_Z, + PLAYER_COLORMAP, PLAYER_VELOCITY, PLAYER_VELOCITY_Z, PLAYER_FLAGS, diff --git a/platform/base_glsl.pk3dir/glsl/defaultskin.glsl b/platform/base_glsl.pk3dir/glsl/defaultskin.glsl index c0d8ec78..d28a18ad 100644 --- a/platform/base_glsl.pk3dir/glsl/defaultskin.glsl +++ b/platform/base_glsl.pk3dir/glsl/defaultskin.glsl @@ -1,200 +1,210 @@ -!!ver 130 -!!permu FRAMEBLEND -!!permu SKELETAL -!!permu FOG -!!samps diffuse reflectcube -!!cvardf gl_affinemodels=0 -!!cvardf gl_ldr=1 -!!cvardf gl_halflambert=1 -!!cvardf gl_mono=0 -!!cvardf gl_kdither=0 -!!cvardf gl_stipplealpha=0 - -!!permu FAKESHADOWS -!!cvardf r_glsl_pcf -!!samps =FAKESHADOWS shadowmap - -!!cvardf r_skipDiffuse - -#include "sys/defs.h" -#include "sys/fog.h" - -#if gl_affinemodels == 1 - #define affine noperspective -#else - #define affine -#endif - -#ifdef REFLECTCUBE -varying vec3 eyevector; -varying mat3 invsurface; -#endif - -#ifdef FAKESHADOWS - varying vec4 vtexprojcoord; -#endif - -affine varying vec2 tex_c; -varying vec3 light; - -#ifdef VERTEX_SHADER - #include "sys/skeletal.h" - - float lambert( vec3 normal, vec3 dir ) { - return dot( normal, dir ); - } - float halflambert( vec3 normal, vec3 dir ) { - return ( dot( normal, dir ) * 0.5 ) + 0.5; - } - -#ifdef CHROME - /* Rotate Light Vector */ - vec3 rlv(vec3 axis, vec3 origin, vec3 lightpoint) - { - vec3 offs; - vec3 result; - offs[0] = lightpoint[0] - origin[0]; - offs[1] = lightpoint[1] - origin[1]; - offs[2] = lightpoint[2] - origin[2]; - result[0] = dot(offs[0], axis[0]); - result[1] = dot(offs[1], axis[1]); - result[2] = dot(offs[2], axis[2]); - return result; - } -#endif - - void main () - { - vec3 n, s, t, w; - gl_Position = skeletaltransform_wnst(w,n,s,t); - tex_c = v_texcoord; - - #if gl_halflambert==1 - light = e_light_ambient + (e_light_mul * halflambert(n, e_light_dir)); - #else - light = e_light_ambient + (e_light_mul * lambert(n, e_light_dir)); - #endif - - light *= e_lmscale.r; - - #if gl_ldr==1 - light *= 0.75; - #endif - -#ifdef CHROME - vec3 rorg = rlv(vec3(0,0,0), w, e_light_dir); - vec3 viewc = normalize(rorg - w); - float d = dot(n, viewc); - vec3 reflected; - reflected.x = n.x * 2.0 * d - viewc.x; - reflected.y = n.y * 2.0 * d - viewc.y; - reflected.z = n.z * 2.0 * d - viewc.z; - tex_c.x = 0.5 + reflected.y * 0.5; - tex_c.y = 0.5 - reflected.z * 0.5; -#endif - -#ifdef REFLECTCUBE - invsurface[0] = v_svector; - invsurface[1] = v_tvector; - invsurface[2] = v_normal; - vec3 eyeminusvertex = e_eyepos - v_position.xyz; - eyevector.x = dot( eyeminusvertex, v_svector.xyz ); - eyevector.y = dot( eyeminusvertex, v_tvector.xyz ); - eyevector.z = dot( eyeminusvertex, v_normal.xyz ); -#endif - } -#endif - - -#ifdef FRAGMENT_SHADER - #include "sys/pcf.h" - - vec4 kernel_dither(sampler2D targ, vec2 texc) - { - int x = int(mod(gl_FragCoord.x, 2.0)); - int y = int(mod(gl_FragCoord.y, 2.0)); - int index = x + y * 2; - vec2 coord_ofs; - vec2 size; - - size.x = 1.0 / textureSize(targ, 0).x; - size.y = 1.0 / textureSize(targ, 0).y; - - if (index == 0) - coord_ofs = vec2(0.25f, 0.0f); - else if (index == 1) - coord_ofs = vec2(0.50f, 0.75f); - else if (index == 2) - coord_ofs = vec2(0.75f, 0.50f); - else if (index == 3) - coord_ofs = vec2(0.00f, 0.25f); - - return texture2D(targ, texc + coord_ofs * size); - } - - void main () - { - vec4 diffuse_f; - -#if r_skipDiffuse==1 - diffuse_f = vec4(1.0, 1.0, 1.0, 1.0); -#else - #if gl_kdither==1 - diffuse_f = kernel_dither(s_diffuse, tex_c); - #else - diffuse_f = texture2D(s_diffuse, tex_c); - #endif -#endif - - diffuse_f.rgb *= light; - -#ifdef REFLECTCUBE - vec3 cube_c; - vec4 out_f = vec4( 1.0, 1.0, 1.0, 1.0 ); - - cube_c = reflect( normalize( -eyevector ), vec3( 0, 0, 1 ) ); - cube_c = cube_c.x * invsurface[0] + cube_c.y * invsurface[1] + cube_c.z * invsurface[2]; - cube_c = ( m_model * vec4( cube_c.xyz, 0.0 ) ).xyz; - out_f.rgb = mix( textureCube( s_reflectcube, cube_c ).rgb, diffuse_f.rgb, diffuse_f.a ); - diffuse_f = out_f; -#endif - - diffuse_f *= e_colourident; - - #if gl_stipplealpha==1 - float alpha = e_colourident.a; - int x = int(mod(gl_FragCoord.x, 2.0)); - int y = int(mod(gl_FragCoord.y, 2.0)); - - if (alpha <= 0.0) { - discard; - } else if (alpha <= 0.25) { - diffuse_f.a = 1.0f; - if (x + y == 2) - discard; - if (x + y == 1) - discard; - } else if (alpha <= 0.5) { - diffuse_f.a = 1.0f; - if (x + y == 2) - discard; - if (x + y == 0) - discard; - } else if (alpha < 1.0) { - diffuse_f.a = 1.0f; - if (x + y == 2) - discard; - } - #endif - - #if gl_mono==1 - float bw = (diffuse_f.r + diffuse_f.g + diffuse_f.b) / 3.0; - diffuse_f.rgb = vec3(bw, bw, bw); - #endif - - #ifdef FAKESHADOWS - diffuse_f.rgb *= ShadowmapFilter(s_shadowmap, vtexprojcoord); - #endif - gl_FragColor = fog4(diffuse_f); - } -#endif +!!ver 130 +!!permu FRAMEBLEND +!!permu SKELETAL +!!permu UPPERLOWER +!!permu FOG +!!samps diffuse reflectcube upper lower +!!cvardf gl_affinemodels=0 +!!cvardf gl_ldr=1 +!!cvardf gl_halflambert=1 +!!cvardf gl_mono=0 +!!cvardf gl_kdither=0 +!!cvardf gl_stipplealpha=0 + +!!permu FAKESHADOWS +!!cvardf r_glsl_pcf +!!samps =FAKESHADOWS shadowmap + +!!cvardf r_skipDiffuse + +#include "sys/defs.h" +#include "sys/fog.h" + +#if gl_affinemodels == 1 + #define affine noperspective +#else + #define affine +#endif + +#ifdef REFLECTCUBE +varying vec3 eyevector; +varying mat3 invsurface; +#endif + +#ifdef FAKESHADOWS + varying vec4 vtexprojcoord; +#endif + +affine varying vec2 tex_c; +varying vec3 light; + +#ifdef VERTEX_SHADER + #include "sys/skeletal.h" + + float lambert( vec3 normal, vec3 dir ) { + return dot( normal, dir ); + } + float halflambert( vec3 normal, vec3 dir ) { + return ( dot( normal, dir ) * 0.5 ) + 0.5; + } + +#ifdef CHROME + /* Rotate Light Vector */ + vec3 rlv(vec3 axis, vec3 origin, vec3 lightpoint) + { + vec3 offs; + vec3 result; + offs[0] = lightpoint[0] - origin[0]; + offs[1] = lightpoint[1] - origin[1]; + offs[2] = lightpoint[2] - origin[2]; + result[0] = dot(offs[0], axis[0]); + result[1] = dot(offs[1], axis[1]); + result[2] = dot(offs[2], axis[2]); + return result; + } +#endif + + void main () + { + vec3 n, s, t, w; + gl_Position = skeletaltransform_wnst(w,n,s,t); + tex_c = v_texcoord; + + #if gl_halflambert==1 + light = e_light_ambient + (e_light_mul * halflambert(n, e_light_dir)); + #else + light = e_light_ambient + (e_light_mul * lambert(n, e_light_dir)); + #endif + + light *= e_lmscale.r; + + #if gl_ldr==1 + light *= 0.75; + #endif + +#ifdef CHROME + vec3 rorg = rlv(vec3(0,0,0), w, e_light_dir); + vec3 viewc = normalize(rorg - w); + float d = dot(n, viewc); + vec3 reflected; + reflected.x = n.x * 2.0 * d - viewc.x; + reflected.y = n.y * 2.0 * d - viewc.y; + reflected.z = n.z * 2.0 * d - viewc.z; + tex_c.x = 0.5 + reflected.y * 0.5; + tex_c.y = 0.5 - reflected.z * 0.5; +#endif + +#ifdef REFLECTCUBE + invsurface[0] = v_svector; + invsurface[1] = v_tvector; + invsurface[2] = v_normal; + vec3 eyeminusvertex = e_eyepos - v_position.xyz; + eyevector.x = dot( eyeminusvertex, v_svector.xyz ); + eyevector.y = dot( eyeminusvertex, v_tvector.xyz ); + eyevector.z = dot( eyeminusvertex, v_normal.xyz ); +#endif + } +#endif + + +#ifdef FRAGMENT_SHADER + #include "sys/pcf.h" + + vec4 kernel_dither(sampler2D targ, vec2 texc) + { + int x = int(mod(gl_FragCoord.x, 2.0)); + int y = int(mod(gl_FragCoord.y, 2.0)); + int index = x + y * 2; + vec2 coord_ofs; + vec2 size; + + size.x = 1.0 / textureSize(targ, 0).x; + size.y = 1.0 / textureSize(targ, 0).y; + + if (index == 0) + coord_ofs = vec2(0.25f, 0.0f); + else if (index == 1) + coord_ofs = vec2(0.50f, 0.75f); + else if (index == 2) + coord_ofs = vec2(0.75f, 0.50f); + else if (index == 3) + coord_ofs = vec2(0.00f, 0.25f); + + return texture2D(targ, texc + coord_ofs * size); + } + + void main () + { + vec4 diffuse_f; + +#if r_skipDiffuse==1 + diffuse_f = vec4(1.0, 1.0, 1.0, 1.0); +#else + #if gl_kdither==1 + diffuse_f = kernel_dither(s_diffuse, tex_c); + #else + diffuse_f = texture2D(s_diffuse, tex_c); + #endif +#endif + + #ifdef UPPER + vec4 uc = texture2D(s_upper, tex_c); + diffuse_f.rgb += uc.rgb*e_uppercolour*uc.a; + #endif + #ifdef LOWER + vec4 lc = texture2D(s_lower, tex_c); + diffuse_f.rgb += lc.rgb*e_lowercolour*lc.a; + #endif + + diffuse_f.rgb *= light; + +#ifdef REFLECTCUBE + vec3 cube_c; + vec4 out_f = vec4( 1.0, 1.0, 1.0, 1.0 ); + + cube_c = reflect( normalize( -eyevector ), vec3( 0, 0, 1 ) ); + cube_c = cube_c.x * invsurface[0] + cube_c.y * invsurface[1] + cube_c.z * invsurface[2]; + cube_c = ( m_model * vec4( cube_c.xyz, 0.0 ) ).xyz; + out_f.rgb = mix( textureCube( s_reflectcube, cube_c ).rgb, diffuse_f.rgb, diffuse_f.a ); + diffuse_f = out_f; +#endif + + diffuse_f *= e_colourident; + + #if gl_stipplealpha==1 + float alpha = e_colourident.a; + int x = int(mod(gl_FragCoord.x, 2.0)); + int y = int(mod(gl_FragCoord.y, 2.0)); + + if (alpha <= 0.0) { + discard; + } else if (alpha <= 0.25) { + diffuse_f.a = 1.0f; + if (x + y == 2) + discard; + if (x + y == 1) + discard; + } else if (alpha <= 0.5) { + diffuse_f.a = 1.0f; + if (x + y == 2) + discard; + if (x + y == 0) + discard; + } else if (alpha < 1.0) { + diffuse_f.a = 1.0f; + if (x + y == 2) + discard; + } + #endif + + #if gl_mono==1 + float bw = (diffuse_f.r + diffuse_f.g + diffuse_f.b) / 3.0; + diffuse_f.rgb = vec3(bw, bw, bw); + #endif + + #ifdef FAKESHADOWS + diffuse_f.rgb *= ShadowmapFilter(s_shadowmap, vtexprojcoord); + #endif + gl_FragColor = fog4(diffuse_f); + } +#endif diff --git a/platform/base_scripts.pk3dir/sound/func_breakable.sndshd b/platform/base_scripts.pk3dir/sound/func_breakable.sndshd index 8a3a2a07..6d5dbe48 100644 --- a/platform/base_scripts.pk3dir/sound/func_breakable.sndshd +++ b/platform/base_scripts.pk3dir/sound/func_breakable.sndshd @@ -53,7 +53,6 @@ func_breakable.impact_cinder sample debris/concrete1.wav sample debris/concrete2.wav sample debris/concrete3.wav - sample debris/concrete4.wav } func_breakable.impact_concrete @@ -68,5 +67,4 @@ func_breakable.impact_rock sample debris/concrete1.wav sample debris/concrete2.wav sample debris/concrete3.wav - sample debris/concrete4.wav } diff --git a/src/client/view.qc b/src/client/view.qc index 0bb02220..fcd6cbbb 100644 --- a/src/client/view.qc +++ b/src/client/view.qc @@ -170,6 +170,7 @@ View_DrawViewModel(void) makevectors(view_angles); m_eViewModel.angles = view_angles; + m_eViewModel.colormap = pSeat->m_ePlayer.colormap; // Give the gun a tilt effect like in old HL/CS versions if (autocvar_v_bobclassic == 1) { diff --git a/src/menu-fn/m_customize.qc b/src/menu-fn/m_customize.qc index fcb4b983..502ca8bf 100644 --- a/src/menu-fn/m_customize.qc +++ b/src/menu-fn/m_customize.qc @@ -20,6 +20,8 @@ CMainButton cz_btnAdvanced; CTextBox cz_tbNetname; CPictureSwitch cz_psSpray; CPictureSwitch cz_psModel; +CSlider cz_sldTopcolor; +CSlider cz_sldBottomcolor; string *g_models; int g_modelcount; @@ -77,6 +79,82 @@ cz_cbSprayChanged(void) localcmd(sprintf("setinfoblob spray %s\n", mdl)); } +vector hsv2rgb(float h, float s, float v) +{ + float i,f,p,q,t; + vector col = [0,0,0]; + + h = max(0.0, min(360.0, h)); + s = max(0.0, min(100.0, s)); + v = max(0.0, min(100.0, v)); + + s /= 100; + v /= 100; + + if (s == 0) { + col[0] = col[1] = col[2] = rint(v*255); + return col; + } + + h /= 60; + i = floor(h); + f = h - i; + p = v * (1 - s); + q = v * (1 - s * f); + t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: + col[0] = rint(255*v); + col[1] = rint(255*t); + col[2] = rint(255*p); + break; + case 1: + col[0] = rint(255*q); + col[1] = rint(255*v); + col[2] = rint(255*p); + break; + case 2: + col[0] = rint(255*p); + col[1] = rint(255*v); + col[2] = rint(255*t); + break; + case 3: + col[0] = rint(255*p); + col[1] = rint(255*q); + col[2] = rint(255*v); + break; + case 4: + col[0] = rint(255*t); + col[1] = rint(255*p); + col[2] = rint(255*v); + break; + default: // case 5: + col[0] = rint(255*v); + col[1] = rint(255*p); + col[2] = rint(255*q); + } + return col; +} + +void +cz_sldTopcolorChanged(float val) +{ + vector x = hsv2rgb(val * 360, 100, 100); + float id = x[2] + (x[1] << 8) + (x[0] << 16); + cvar_set("topcolor", sprintf("0x%x", id)); + localcmd(sprintf("seta _cl_topcolor %f\n", val)); +} + +void +cz_sldBottomcolorChanged(float val) +{ + vector x = hsv2rgb(val * 360, 100, 100); + float id = x[2] + (x[1] << 8) + (x[0] << 16); + cvar_set("bottomcolor", sprintf("0x%x", id)); + localcmd(sprintf("seta _cl_bottomcolor %f\n", val)); +} + void menu_customize_init(void) { @@ -136,6 +214,19 @@ menu_customize_init(void) cz_tbNetname.SetPos(212,160); cz_tbNetname.SetText(cvar_string("name")); Widget_Add(fn_customize, cz_tbNetname); + + cz_sldTopcolor = spawn(CSlider); + cz_sldTopcolor.SetPos(420,400); + cz_sldTopcolor.SetValue(cvar("_cl_topcolor")); + cz_sldTopcolor.SetCallback(cz_sldTopcolorChanged); + Widget_Add(fn_customize, cz_sldTopcolor); + + cz_sldBottomcolor = spawn(CSlider); + cz_sldBottomcolor.SetPos(420,420); + cz_sldBottomcolor.SetValue(cvar("_cl_bottomcolor")); + cz_sldBottomcolor.SetCallback(cz_sldBottomcolorChanged); + Widget_Add(fn_customize, cz_sldBottomcolor); + if (games[gameinfo_current].nosprays == 0) { cz_psSpray = spawn(CPictureSwitch); @@ -171,9 +262,16 @@ menu_customize_draw(void) WLabel_Static(212, 140, m_reslbl[IDS_PLAYERINFO_NAME], 14, 14, [1,1,1], 1.0f, 0, font_arial); - if (games[gameinfo_current].nomodels == 0) - WLabel_Static(410, 140, sprintf(m_reslbl[IDS_MODEL_NAME], cvar_string("_cl_playermodel")), 14, 14, [1,1,1], - 1.0f, 0, font_arial); + if (games[gameinfo_current].nomodels == 0) { + WLabel_Static(410, 140, sprintf(m_reslbl[IDS_MODEL_NAME], cvar_string("_cl_playermodel")), 14, 14, [1,1,1], + 1.0f, 0, font_arial); + + vector t, b; + t = hsv2rgb(cvar("_cl_topcolor") * 360, 100, 100) / 255; + b = hsv2rgb(cvar("_cl_bottomcolor") * 360, 100, 100) / 255; + drawfill([g_menuofs[0] + 414,g_menuofs[1] + 346], [64,12], t, 0.75f); + drawfill([g_menuofs[0] + 510,g_menuofs[1] + 346], [64,12], b, 0.75f); + } if (games[gameinfo_current].nosprays == 0) WLabel_Static(212, 203, m_reslbl[IDS_PROFILE_LOGO], 14, 14, [1,1,1], diff --git a/src/shared/player.h b/src/shared/player.h index 46fd67fa..d673a2be 100644 --- a/src/shared/player.h +++ b/src/shared/player.h @@ -41,6 +41,7 @@ base_player PREDICTED_VECTOR_N(origin); PREDICTED_VECTOR_N(velocity); PREDICTED_VECTOR_N(angles); + PREDICTED_FLOAT_N(colormap); PREDICTED_FLOAT_N(flags); PREDICTED_FLOAT_N(gflags); PREDICTED_FLOAT(viewzoom); diff --git a/src/shared/player.qc b/src/shared/player.qc index b20060c8..dfb3cdfc 100644 --- a/src/shared/player.qc +++ b/src/shared/player.qc @@ -52,8 +52,8 @@ base_player::ReceiveEntity(float new, float fl) pitch = readfloat(); if (fl & PLAYER_ANGLES_Y) angles[1] = readfloat(); - if (fl & PLAYER_ANGLES_Z) - angles[2] = readfloat(); + if (fl & PLAYER_COLORMAP) + colormap = readfloat(); if (fl & PLAYER_VELOCITY) { velocity[0] = readcoord(); @@ -106,6 +106,7 @@ base_player::PredictPreFrame(void) SAVE_STATE(modelindex); SAVE_STATE(origin); SAVE_STATE(angles); + SAVE_STATE(colormap); SAVE_STATE(velocity); SAVE_STATE(flags); SAVE_STATE(gflags); @@ -142,6 +143,7 @@ base_player::PredictPostFrame(void) ROLL_BACK(modelindex); ROLL_BACK(origin); ROLL_BACK(angles); + ROLL_BACK(colormap); ROLL_BACK(velocity); ROLL_BACK(flags); ROLL_BACK(gflags); @@ -193,8 +195,8 @@ base_player::EvaluateEntity(void) if (VEC_CHANGED(angles, 1)) SendFlags |= PLAYER_ANGLES_Y; - if (VEC_CHANGED(angles, 2)) - SendFlags |= PLAYER_ANGLES_Z; + if (ATTR_CHANGED(colormap)) + SendFlags |= PLAYER_COLORMAP; if (VEC_CHANGED(velocity, 0)) SendFlags |= PLAYER_VELOCITY; @@ -232,6 +234,7 @@ base_player::EvaluateEntity(void) SAVE_STATE(modelindex); SAVE_STATE(origin); SAVE_STATE(angles); + SAVE_STATE(colormap); SAVE_STATE(velocity); SAVE_STATE(flags); SAVE_STATE(gflags); @@ -281,8 +284,8 @@ base_player::SendEntity(entity ePEnt, float fChanged) WriteFloat(MSG_ENTITY, v_angle[0]); if (fChanged & PLAYER_ANGLES_Y) WriteFloat(MSG_ENTITY, angles[1]); - if (fChanged & PLAYER_ANGLES_Z) - WriteFloat(MSG_ENTITY, angles[2]); + if (fChanged & PLAYER_COLORMAP) + WriteFloat(MSG_ENTITY, colormap); /* similar as with origin, we separate x/y from z */ if (fChanged & PLAYER_VELOCITY) {