Quake 3 DeFRaG 1.91.26 - CampingGaz-Hud (speedometer, accel bar, compass, cgaz) This article provides the source code for a custom HUD element called "CampingGaz-Hud" for the game Quake 3 DeFRaG version 1.91.26. The code implements a speedometer that displays the player's speed in units per second, as well as an acceleration bar that visually indicates positive or negative acceleration. The HUD also includes a compass and other display features controlled by the `cgaz` function. CGaz StrafeHud.c This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters define M PI 3.14159274101f typedef struct { vec3 t prev vel; // 0x0 float prev width; // 0xc int prev commandTime; // 0x10 } accelBarState t; accelBarState t accelBarState; // 0x2b0bb0 void cgaz int draw, float opacity, int ypos { if cg.snap || cg.hyperspace { return; } // speedometer if draw & 2 { int const charWidthBig = 32; int const charWidthSmall = 3 charWidthBig / 4; float const integer = floor cg.xyspeed ; // integer-part char const str = va "^%d%4.0f", 7, integer ; int const len = CG DrawStrlen str ; CG DrawStringExt 640 - len charWidthBig - 5 charWidthSmall / x /, 480 - int 1.5f 2.5f charWidthBig / y /, str, colorWhite, qfalse / forceColor /, qtrue / shadow /, charWidthBig, int 1.5f charWidthBig / charHeight /, 0 / maxChars / ; // fractional-part str = va "%0.01f^3u/s", cg.xyspeed - integer ; assert CG DrawStrlen str == 5 ; CG DrawStringExt 640 - 5 charWidthSmall / x /, 480 - int 1.5f 2.375f charWidthBig / y /, str + 1, // start at decimal point, skip 0 colorWhite, qfalse / forceColor /, qtrue / shadow /, charWidthSmall, int 1.5f charWidthSmall / charHeight /, 0 / maxChars / ; } if draw & 1 { return; } vec4 t white = { 1, 1, 1, 1 }; vec4 t blue = { .5, .5, 1, 1 }; vec4 t orange = { 1, .5, 0, 1 }; float opacity2 = 2.f opacity; if opacity2 1.f { opacity2 = 1.f; } white 3 = opacity2; blue 3 = opacity2; orange 3 = opacity2; // accel bar if draw & 4 { vec4 t red = { 1, 0, 0, .8f }; red 3 = opacity; playerState t const ps; if cg.demoPlayback || cg.snap- ps.pm flags & PMF FOLLOW || cg nopredict.integer || cg synchronousClients.integer { ps = cg.nextSnap ? &cg.nextSnap- ps : &cg.snap- ps; } else { ps = &cg.predictedPlayerState; } float width; if ps- commandTime <= accelBarState.prev commandTime { width = accelBarState.prev width; } else { vec3 t vel xy; memcpy vel xy, ps- velocity, sizeof vec3 t ; vel xy 2 = 0; float const speed = VectorLength vel xy ; float const prev speed = VectorLength accelBarState.prev vel ; width = speed - prev speed / ps- commandTime - accelBarState.prev commandTime ; width = 256.f; if ps- groundEntityNum = ENTITYNUM NONE { width = .1f; } // update prev state memcpy accelBarState.prev vel, vel xy, sizeof vec3 t ; accelBarState.prev commandTime = ps- commandTime; accelBarState.prev width = width; } if width 0 { CG FillRect cg crosshairSize.integer + 320.f, 232.f, width, 16.f, red ; } else if width < 0 { CG FillRect 320.f - cg crosshairSize.integer, 232.f, width, 16.f, red ; } trap R SetColor colorWhite ; CG FillRect cg crosshairSize.integer + 320 + 81.92f, 236.f, 1.f, 8.f, white ; CG FillRect 320 - cg crosshairSize.integer - 81.92f, 236.f, -1.f, 8.f, white ; } float const tan fov 2 = tan DEG2RAD cg.refdef.fov x / 2.f ; // compass quadrants & ticks if draw & 8 { vec4 t colors 4 = { { 1, 1, .5, .5 }, { 0, 1, .5, .5 }, { 0, 0, .5, .5 }, { 1, 0, .5, .5 }, }; colors 0 3 = opacity; colors 1 3 = opacity; colors 2 3 = opacity; colors 3 3 = opacity; for float angle = 0.f; angle < 360.f; angle += 45.f { float angle a = AngleSubtract cg.predictedPlayerState.viewangles YAW , angle ; float angle b = AngleSubtract cg.predictedPlayerState.viewangles YAW , angle + 90.f ; // clip if fabs angle a cg.refdef.fov x / 2.f { angle a = copysign cg.refdef.fov x / 2.f, angle a ; } if fabs angle b cg.refdef.fov x / 2.f { angle b = copysign cg.refdef.fov x / 2.f, angle b ; } // project float const x a = 320.f tan DEG2RAD angle a / tan fov 2; float const x b = 320.f tan DEG2RAD angle b / tan fov 2; if x a = x b { qboolean const mainAxis = fmodf angle, 90.f == 0; if mainAxis { int const quadrant = int angle / 90.f ; CG FillRect x b + 320.f, ypos + 16.f, x a - x b, 16.f, colors quadrant ; } // tick trap R SetColor white ; float const h = mainAxis ? 8 : 4;; CG FillRect x a + 320.f - 1.f, ypos + 16.f + 16.f - h, 2.f, h, white ; } } } if fabs cg.predictedPlayerState.velocity 0 + fabs cg.predictedPlayerState.velocity 1 == 0 { return; } float const vel dir = RAD2DEG atan2 cg.predictedPlayerState.velocity 1 , cg.predictedPlayerState.velocity 0 ; // compass arrow if draw & 8 { vec4 t const arrows 2 = { { .439453125f, .5, .498046875f, .55859375f }, // char 135 - arrow pointing up { .373046875f, .5, .431640625f, .55859375f }, // char 134 - arrow pointing down }; enum { up = 0, down = 1 } dir = up; float angle = AngleSubtract cg.predictedPlayerState.viewangles YAW , vel dir ; if angle 90.f { // flip arrow dir = down; angle -= 180.f; } // clip if fabs angle cg.refdef.fov x / 2.f { angle = copysign cg.refdef.fov x / 2.f, angle ; } // project float x = 320.f tan DEG2RAD angle / tan fov 2 + 320.f - 8.f; float y = ypos + 32.f; float w = 16.f; float h = 16.f; CG AdjustFrom640 &x, &y, &w, &h ; trap R SetColor cg.predictedPlayerState.velocity 0 cg.predictedPlayerState.velocity 1 = 0 ? white : orange ; trap R DrawStretchPic x, y, w, h, arrows dir 0 , arrows dir 1 , arrows dir 2 , arrows dir 3 , cgs.media.charsetShader ; } // cgaz if draw & 16 { vec3 t vel xy; vel xy 0 = cg.predictedPlayerState.velocity 0 ; vel xy 1 = cg.predictedPlayerState.velocity 1 ; vel xy 2 = 0; float speed = VectorLength vel xy ; float accel = float cg.snap- ps.speed pmove msec.value / 1000.f ; if cg.predictedPlayerState.groundEntityNum = ENTITYNUM NONE { accel = 10.f; // hard coded for vq3 speed = 1.f - 6.f pmove msec.value / 1000.f ; // friction } if speed <= cg.snap- ps.speed - accel { return; } vec4 t colors 8 = { { 1, 1, 0, 1 }, // yellow { 0, .25, .25, 1 }, // cyan { 0, 1, 0, 1 }, // green { .25, .25, .25, 1 }, // grey { .25, .25, .25, 1 }, // grey { 0, 1, 0, 1 }, // green { 0, .25, .25, 1 }, // cyan { 1, 1, 0, 1 }, // yellow }; colors 0 3 = opacity; colors 1 3 = opacity; colors 2 3 = opacity; colors 3 3 = opacity; colors 4 3 = opacity; colors 5 3 = opacity; colors 6 3 = opacity; colors 7 3 = opacity; float yaw = cg.predictedPlayerState.viewangles YAW ; // 0x1b = 0x1 | 0x2 | 0x8 | 0x10 = KEY FORWARD | KEY BACK | KEY LEFT | KEY RIGHT // defrag server = 1 when CS SERVERINFO contains defrag vers or defrag version if cg.predictedPlayerState.stats 13 & 0x1b || defrag server { yaw += 45.f cg.predictedPlayerState.movementDir; } float const max angle = RAD2DEG acos -.5f accel / speed ; float const opt angle = RAD2DEG acos cg.snap- ps.speed - accel / speed ; float const min angle = speed cg.snap- ps.speed ? RAD2DEG acos cg.snap- ps.speed / speed : 0; float angles 9 = { vel dir - max angle, vel dir - 90.f, vel dir - opt angle, vel dir - min angle, vel dir, vel dir + min angle, vel dir + opt angle, vel dir + 90.f, vel dir + max angle, }; for int i = 0; i < 8; ++i { float angle a = AngleSubtract yaw, angles i ; float angle b = AngleSubtract yaw, angles i + 1 ; // clip if fabs angle a cg.refdef.fov x / 2.f { angle a = copysign cg.refdef.fov x / 2.f, angle a ; } if fabs angle b cg.refdef.fov x / 2.f { angle b = copysign cg.refdef.fov x / 2.f, angle b ; } // project float const x a = 320.f tan DEG2RAD angle a / tan fov 2; float const x b = 320.f tan DEG2RAD angle b / tan fov 2; if x a = x b { CG FillRect x b + 320.f, float ypos, x a - x b, 16.f, colors i ; if i == 4 { CG FillRect x a + 320.f - 1.f, ypos + 2.f, 2.f, 12.f, blue ; // little blue bar in the middle } } } } }