#include "quakedef.h" #ifndef SERVERONLY #include "shader.h" #include "gl_draw.h" texid_t missing_texture; texid_t translate_texture; shader_t *translate_shader; texid_t ch_int_texture; vec3_t ch_color; shader_t *shader_crosshair; static mpic_t *conback; static mpic_t *draw_backtile; static shader_t *shader_draw_fill, *shader_draw_fill_trans; mpic_t *draw_disc; shader_t *shader_brighten; shader_t *shader_polyblend; shader_t *shader_menutint; static mesh_t draw_mesh; static vecV_t draw_mesh_xyz[4]; vec2_t draw_mesh_st[4]; static avec4_t draw_mesh_colors[4]; index_t r_quad_indexes[6] = {0, 1, 2, 2, 3, 0}; unsigned int r2d_be_flags; extern cvar_t scr_conalpha; extern cvar_t gl_conback; extern cvar_t gl_font; extern cvar_t gl_contrast; extern cvar_t gl_screenangle; extern cvar_t vid_conautoscale; extern cvar_t vid_conheight; extern cvar_t vid_conwidth; void R2D_Font_Callback(struct cvar_s *var, char *oldvalue); void R2D_Conautoscale_Callback(struct cvar_s *var, char *oldvalue); void R2D_ScreenAngle_Callback(struct cvar_s *var, char *oldvalue); void R2D_Conheight_Callback(struct cvar_s *var, char *oldvalue); void R2D_Conwidth_Callback(struct cvar_s *var, char *oldvalue); extern cvar_t crosshair; extern cvar_t crosshaircolor; extern cvar_t crosshairsize; extern cvar_t crosshairimage; extern cvar_t crosshairalpha; void R2D_Crosshair_Callback(struct cvar_s *var, char *oldvalue); void R2D_CrosshairImage_Callback(struct cvar_s *var, char *oldvalue); void R2D_CrosshairColor_Callback(struct cvar_s *var, char *oldvalue); //We need this for minor things though, so we'll just use the slow accurate method. //this is unlikly to be called too often. qbyte GetPaletteIndex(int red, int green, int blue) { //slow, horrible method. { int i, best=15; int bestdif=256*256*256, curdif; extern qbyte *host_basepal; qbyte *pa; #define _abs(x) ((x)*(x)) pa = host_basepal; for (i = 0; i < 256; i++, pa+=3) { curdif = _abs(red - pa[0]) + _abs(green - pa[1]) + _abs(blue - pa[2]); if (curdif < bestdif) { if (curdif<1) return i; bestdif = curdif; best = i; } } return best; } } /* Iniitalise the 2d rendering functions (including font). Image loading code must be ready for use at this point. */ void R2D_Init(void) { conback = NULL; Shader_Init(); BE_Init(); draw_mesh.istrifan = true; draw_mesh.numvertexes = 4; draw_mesh.numindexes = 6; draw_mesh.xyz_array = draw_mesh_xyz; draw_mesh.st_array = draw_mesh_st; draw_mesh.colors4f_array = draw_mesh_colors; draw_mesh.indexes = r_quad_indexes; Font_Init(); #ifdef _MSC_VER #pragma message("Fixme: move conwidth handling into here") #endif missing_texture = R_LoadTexture8("no_texture", 16, 16, (unsigned char*)r_notexture_mip + r_notexture_mip->offsets[0], IF_NOALPHA|IF_NOGAMMA, 0); translate_texture = r_nulltex; ch_int_texture = r_nulltex; draw_backtile = R_RegisterShader("gfx/backtile.lmp", "{\n" "if $nofixed\n" "[\n" "program default2d\n" "]\n" "nomipmaps\n" "{\n" "map $diffuse\n" "}\n" "}\n"); if (!TEXVALID(draw_backtile->defaulttextures.base)) draw_backtile->defaulttextures.base = R_LoadHiResTexture("gfx/backtile", NULL, IF_NOPICMIP|IF_NOMIPMAP); if (!TEXVALID(draw_backtile->defaulttextures.base)) draw_backtile->defaulttextures.base = R_LoadHiResTexture("gfx/menu/backtile", NULL, IF_NOPICMIP|IF_NOMIPMAP); shader_draw_fill = R_RegisterShader("fill_opaque", "{\n" "program defaultfill\n" "{\n" "map $whiteimage\n" "rgbgen vertex\n" "}\n" "}\n"); shader_draw_fill_trans = R_RegisterShader("fill_trans", "{\n" "program defaultfill\n" "{\n" "map $whiteimage\n" "rgbgen vertex\n" "alphagen vertex\n" "blendfunc blend\n" "}\n" "}\n"); shader_brighten = R_RegisterShader("constrastshader", "{\n" "program defaultfill\n" "{\n" "map $whiteimage\n" "blendfunc gl_dst_color gl_one\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}\n" ); shader_polyblend = R_RegisterShader("polyblendshader", "{\n" "program defaultfill\n" "{\n" "map $whiteimage\n" "blendfunc gl_src_alpha gl_one_minus_src_alpha\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}\n" ); shader_menutint = R_RegisterShader("menutint_glsl", "{\n" "if $glsl && gl_menutint_shader != 0\n" "[\n" "glslprogram\n" "{\n" "#ifdef VERTEX_SHADER\n" "\ attribute vec2 v_texcoord;\ varying vec2 texcoord;\ uniform vec3 rendertexturescale;\ void main(void)\ {\ texcoord.x = v_texcoord.x*rendertexturescale.x;\ texcoord.y = (1.0-v_texcoord.y)*rendertexturescale.y;\ gl_Position = ftetransform();\ }\ \n" "#endif\n" "#ifdef FRAGMENT_SHADER\n" "\ varying vec2 texcoord;\ uniform vec3 colorparam;\ uniform sampler2D s_t0;\ uniform int invert;\ const vec3 lumfactors = vec3(0.299, 0.587, 0.114);\ const vec3 invertvec = vec3(1.0, 1.0, 1.0);\ void main(void)\ {\ vec3 texcolor = texture2D(s_t0, texcoord).rgb;\ float luminance = dot(lumfactors, texcolor);\ texcolor = vec3(luminance, luminance, luminance);\ texcolor *= colorparam;\ texcolor = (invert > 0) ? (invertvec - texcolor) : texcolor;\ gl_FragColor = vec4(texcolor, 1.0);\ }\n" "#endif\n" "}\n" "param cvari r_menutint_inverse invert\n" "param cvar3f r_menutint colorparam\n" "param rendertexturescale rendertexturescale\n" "{\n" "map $currentrender\n" "}\n" "][\n" "{\n" "map $whitetexture\n" "blendfunc gl_dst_color gl_zero\n" "rgbgen const $r_menutint\n" "}\n" "]\n" "}\n" ); shader_crosshair = R_RegisterShader("crosshairshader", "{\n" "if $nofixed\n" "[\n" "program default2d\n" "]\n" "nomipmaps\n" "{\n" "map $diffuse\n" "blendfunc blend\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}\n" ); Cvar_Hook(&gl_font, R2D_Font_Callback); Cvar_Hook(&vid_conautoscale, R2D_Conautoscale_Callback); Cvar_Hook(&gl_screenangle, R2D_ScreenAngle_Callback); Cvar_Hook(&vid_conheight, R2D_Conheight_Callback); Cvar_Hook(&vid_conwidth, R2D_Conwidth_Callback); Cvar_Hook(&crosshair, R2D_Crosshair_Callback); Cvar_Hook(&crosshairimage, R2D_CrosshairImage_Callback); Cvar_Hook(&crosshaircolor, R2D_CrosshairColor_Callback); Cvar_ForceCallback(&gl_conback); Cvar_ForceCallback(&vid_conautoscale); Cvar_ForceCallback(&gl_font); Cvar_ForceCallback(&crosshair); Cvar_ForceCallback(&crosshaircolor); #ifdef PLUGINS Plug_DrawReloadImages(); #endif } mpic_t *R2D_SafeCachePic (char *path) { shader_t *s; if (!qrenderer) return NULL; s = R_RegisterPic(path); if (s->flags & SHADER_NOIMAGE) return NULL; return s; } char *failedpic; //easier this way mpic_t *R2D_SafePicFromWad (char *name) { char newname[32]; shader_t *s; snprintf(newname, sizeof(newname), "gfx/%s.lmp", name); s = R_RegisterPic(newname); if (!(s->flags & SHADER_NOIMAGE)) return s; failedpic = name; return NULL; } void R2D_ImageColours(float r, float g, float b, float a) { draw_mesh_colors[0][0] = r; draw_mesh_colors[0][1] = g; draw_mesh_colors[0][2] = b; draw_mesh_colors[0][3] = a; Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[1]); Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[2]); Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[3]); } void R2D_ImagePaletteColour(unsigned int i, float a) { draw_mesh_colors[0][0] = host_basepal[i*3+0]/255.0; draw_mesh_colors[0][1] = host_basepal[i*3+1]/255.0; draw_mesh_colors[0][2] = host_basepal[i*3+2]/255.0; draw_mesh_colors[0][3] = a; Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[1]); Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[2]); Vector4Copy(draw_mesh_colors[0], draw_mesh_colors[3]); } //awkward and weird to use void R2D_Image(float x, float y, float w, float h, float s1, float t1, float s2, float t2, mpic_t *pic) { if (!pic) return; /* if (w == 0 && h == 0) { w = pic->width; h = pic->height; } */ draw_mesh_xyz[0][0] = x; draw_mesh_xyz[0][1] = y; draw_mesh_st[0][0] = s1; draw_mesh_st[0][1] = t1; draw_mesh_xyz[1][0] = x+w; draw_mesh_xyz[1][1] = y; draw_mesh_st[1][0] = s2; draw_mesh_st[1][1] = t1; draw_mesh_xyz[2][0] = x+w; draw_mesh_xyz[2][1] = y+h; draw_mesh_st[2][0] = s2; draw_mesh_st[2][1] = t2; draw_mesh_xyz[3][0] = x; draw_mesh_xyz[3][1] = y+h; draw_mesh_st[3][0] = s1; draw_mesh_st[3][1] = t2; BE_DrawMesh_Single(pic, &draw_mesh, NULL, &pic->defaulttextures, r2d_be_flags); } /*draws a block of the current colour on the screen*/ void R2D_FillBlock(int x, int y, int w, int h) { draw_mesh_xyz[0][0] = x; draw_mesh_xyz[0][1] = y; draw_mesh_xyz[1][0] = x+w; draw_mesh_xyz[1][1] = y; draw_mesh_xyz[2][0] = x+w; draw_mesh_xyz[2][1] = y+h; draw_mesh_xyz[3][0] = x; draw_mesh_xyz[3][1] = y+h; if (draw_mesh_colors[0][3] != 1) BE_DrawMesh_Single(shader_draw_fill_trans, &draw_mesh, NULL, &shader_draw_fill_trans->defaulttextures, r2d_be_flags); else BE_DrawMesh_Single(shader_draw_fill, &draw_mesh, NULL, &shader_draw_fill->defaulttextures, r2d_be_flags); } void R2D_ScalePic (int x, int y, int width, int height, mpic_t *pic) { R2D_Image(x, y, width, height, 0, 0, 1, 1, pic); } void R2D_SubPic(int x, int y, int width, int height, mpic_t *pic, int srcx, int srcy, int srcwidth, int srcheight) { float newsl, newtl, newsh, newth; newsl = (srcx)/(float)srcwidth; newsh = newsl + (width)/(float)srcwidth; newtl = (srcy)/(float)srcheight; newth = newtl + (height)/(float)srcheight; R2D_Image(x, y, width, height, newsl, newtl, newsh, newth, pic); } /* this is an ugly special case drawing func that's only used for the player color selection menu */ void R2D_TransPicTranslate (int x, int y, int width, int height, qbyte *pic, qbyte *translation) { int v, u, c; unsigned trans[64*64], *dest; qbyte *src; int p; c = width * height; dest = trans; for (v=0 ; v<64 ; v++, dest += 64) { src = &pic[ ((v*height)>>6) *width]; for (u=0 ; u<64 ; u++) { p = src[(u*width)>>6]; if (p == 255) dest[u] = 0x0; else dest[u] = d_8to24rgbtable[translation[p]]; } } if (!TEXVALID(translate_texture)) { translate_texture = R_AllocNewTexture(64, 64); translate_shader = R_RegisterShader("translatedpic", "{\n" "if $nofixed\n" "[\n" "program default2d\n" "]\n" "nomipmaps\n" "{\n" "map $diffuse\n" "blendfunc blend\n" "}\n" "}\n"); translate_shader->defaulttextures.base = translate_texture; } /* could avoid reuploading already translated textures but this func really isn't used enough anyway */ R_Upload(translate_texture, NULL, TF_RGBA32, trans, NULL, 64, 64, IF_NOMIPMAP|IF_NOGAMMA); R2D_ScalePic(x, y, width, height, translate_shader); } /* ================ Draw_ConsoleBackground ================ */ void R2D_ConsoleBackground (int firstline, int lastline, qboolean forceopaque) { float a; int w, h; if (!conback) return; w = vid.width; h = vid.height; if (forceopaque) { a = 1; // console background is necessary } else { if (!scr_conalpha.value) return; a = scr_conalpha.value; } if (scr_chatmode == 2) { h>>=1; w>>=1; } if (a >= 1) { R2D_ImageColours(1, 1, 1, 1); R2D_ScalePic(0, lastline-(int)vid.height, w, h, conback); } else { R2D_ImageColours(1, 1, 1, a); R2D_ScalePic (0, lastline - (int)vid.height, w, h, conback); R2D_ImageColours(1, 1, 1, 1); } } void R2D_EditorBackground (void) { R2D_ScalePic(0, 0, vid.width, vid.height, conback); } /* ============= Draw_TileClear This repeats a 64*64 tile graphic to fill the screen around a sized down refresh window. ============= */ void R2D_TileClear (int x, int y, int w, int h) { float newsl, newsh, newtl, newth; newsl = (x)/(float)64; newsh = newsl + (w)/(float)64; newtl = (y)/(float)64; newth = newtl + (h)/(float)64; R2D_ImageColours(1,1,1,1); draw_mesh_xyz[0][0] = x; draw_mesh_xyz[0][1] = y; draw_mesh_st[0][0] = newsl; draw_mesh_st[0][1] = newtl; draw_mesh_xyz[1][0] = x+w; draw_mesh_xyz[1][1] = y; draw_mesh_st[1][0] = newsh; draw_mesh_st[1][1] = newtl; draw_mesh_xyz[2][0] = x+w; draw_mesh_xyz[2][1] = y+h; draw_mesh_st[2][0] = newsh; draw_mesh_st[2][1] = newth; draw_mesh_xyz[3][0] = x; draw_mesh_xyz[3][1] = y+h; draw_mesh_st[3][0] = newsl; draw_mesh_st[3][1] = newth; BE_DrawMesh_Single(draw_backtile, &draw_mesh, NULL, &draw_backtile->defaulttextures, r2d_be_flags); } void R2D_Conback_Callback(struct cvar_s *var, char *oldvalue) { if (qrenderer == QR_NONE) { conback = NULL; return; } if (*var->string) conback = R_RegisterPic(var->string); if (!conback || conback->flags & SHADER_NOIMAGE) { conback = R_RegisterCustom("console", NULL, NULL); if (!conback || conback->flags & SHADER_NOIMAGE) { if (M_GameType() == MGT_HEXEN2) conback = R_RegisterPic("gfx/menu/conback.lmp"); else if (M_GameType() == MGT_QUAKE2) conback = R_RegisterPic("pics/conback.pcx"); else conback = R_RegisterPic("gfx/conback.lmp"); } } } void R2D_Font_Callback(struct cvar_s *var, char *oldvalue) { if (font_conchar) Font_Free(font_conchar); if (qrenderer == QR_NONE) { font_conchar = NULL; return; } font_conchar = Font_LoadFont(8*vid.rotpixelheight/vid.height, var->string); if (!font_conchar && *var->string) font_conchar = Font_LoadFont(8*vid.rotpixelheight/vid.height, ""); } // console size manipulation callbacks void R2D_Console_Resize(void) { extern cvar_t gl_font; extern cvar_t vid_conwidth, vid_conheight; int cwidth, cheight; float xratio; float yratio=0; float ang, rad; extern cvar_t gl_screenangle; ang = (gl_screenangle.value>0?(gl_screenangle.value+45):(gl_screenangle.value-45))/90; ang = (int)ang * 90; if (ang) { rad = (ang * M_PI) / 180; vid.rotpixelwidth = fabs(cos(rad)) * (vid.pixelwidth) + fabs(sin(rad)) * (vid.pixelheight); vid.rotpixelheight = fabs(sin(rad)) * (vid.pixelwidth) + fabs(cos(rad)) * (vid.pixelheight); } else { vid.rotpixelwidth = vid.pixelwidth; vid.rotpixelheight = vid.pixelheight; } cwidth = vid_conwidth.value; cheight = vid_conheight.value; xratio = vid_conautoscale.value; if (xratio > 0) { char *s = strchr(vid_conautoscale.string, ' '); if (s) yratio = atof(s + 1); if (yratio <= 0) yratio = xratio; xratio = 1 / xratio; yratio = 1 / yratio; //autoscale overrides conwidth/height (without actually changing them) cwidth = vid.rotpixelwidth; cheight = vid.rotpixelheight; } else { xratio = 1; yratio = 1; } if (!cwidth) cwidth = vid.rotpixelwidth; if (!cheight) cheight = vid.rotpixelheight; cwidth*=xratio; cheight*=yratio; if (cwidth < 320) cwidth = 320; if (cheight < 200) cheight = 200; vid.width = cwidth; vid.height = cheight; vid.recalc_refdef = true; if (font_tiny) Font_Free(font_tiny); font_tiny = NULL; if (font_conchar) Font_Free(font_conchar); font_conchar = NULL; Cvar_ForceCallback(&gl_font); #ifdef PLUGINS Plug_ResChanged(); #endif } void R2D_Conheight_Callback(struct cvar_s *var, char *oldvalue) { if (var->value > 1536) //anything higher is unreadable. { Cvar_ForceSet(var, "1536"); return; } if (var->value < 200 && var->value) //lower would be wrong { Cvar_ForceSet(var, "200"); return; } R2D_Console_Resize(); } void R2D_Conwidth_Callback(struct cvar_s *var, char *oldvalue) { //let let the user be too crazy if (var->value > 2048) //anything higher is unreadable. { Cvar_ForceSet(var, "2048"); return; } if (var->value < 320 && var->value) //lower would be wrong { Cvar_ForceSet(var, "320"); return; } R2D_Console_Resize(); } void R2D_Conautoscale_Callback(struct cvar_s *var, char *oldvalue) { R2D_Console_Resize(); } void R2D_ScreenAngle_Callback(struct cvar_s *var, char *oldvalue) { R2D_Console_Resize(); } /* ============ R_PolyBlend ============ */ //bright flashes and stuff void R2D_PolyBlend (void) { if (!sw_blend[3]) return; if (r_refdef.flags & Q2RDF_NOWORLDMODEL) return; R2D_ImageColours (sw_blend[0], sw_blend[1], sw_blend[2], sw_blend[3]); R2D_ScalePic(0, 0, vid.width, vid.height, shader_polyblend); R2D_ImageColours (1, 1, 1, 1); } //for lack of hardware gamma void R2D_BrightenScreen (void) { float f; RSpeedMark(); if (gl_contrast.value <= 1.0) return; if (r_refdef.flags & Q2RDF_NOWORLDMODEL) return; f = gl_contrast.value; f = min (f, 3); while (f > 1) { if (f >= 2) R2D_ImageColours (1, 1, 1, 1); else R2D_ImageColours (f - 1, f - 1, f - 1, 1); R2D_ScalePic(0, 0, vid.width, vid.height, shader_brighten); f *= 0.5; } R2D_ImageColours (1, 1, 1, 1); RSpeedEnd(RSPEED_PALETTEFLASHES); } //for menus void R2D_FadeScreen (void) { R2D_ScalePic(0, 0, vid.width, vid.height, shader_menutint); Sbar_Changed(); } //crosshairs #define CS_HEIGHT 8 #define CS_WIDTH 8 unsigned char crosshair_pixels[] = { // 2 + (spaced) 0x08, 0x00, 0x08, 0x55, 0x08, 0x00, 0x08, 0x00, // 3 + 0x00, 0x08, 0x08, 0x36, 0x08, 0x08, 0x00, 0x00, // 4 X 0x00, 0x22, 0x14, 0x00, 0x14, 0x22, 0x00, 0x00, // 5 X (spaced) 0x41, 0x00, 0x14, 0x00, 0x14, 0x00, 0x41, 0x00, // 6 diamond (unconnected) 0x00, 0x14, 0x22, 0x00, 0x22, 0x14, 0x00, 0x00, // 7 diamond 0x00, 0x08, 0x14, 0x22, 0x14, 0x08, 0x00, 0x00, // 8 four points 0x00, 0x08, 0x00, 0x22, 0x00, 0x08, 0x00, 0x00, // 9 three points 0x00, 0x00, 0x08, 0x00, 0x22, 0x00, 0x00, 0x00, // 10 0x08, 0x2a, 0x00, 0x63, 0x00, 0x2a, 0x08, 0x00, // 11 0x49, 0x2a, 0x00, 0x63, 0x00, 0x2a, 0x49, 0x00, // 12 horizontal line 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, // 13 vertical line 0x08, 0x08, 0x08, 0x00, 0x08, 0x08, 0x08, 0x00, // 14 larger + 0x08, 0x08, 0x08, 0x77, 0x08, 0x08, 0x08, 0x00, // 15 angle 0x00, 0x00, 0x00, 0x70, 0x08, 0x08, 0x08, 0x00, // 16 dot 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, // 17 weird angle thing 0x00, 0x00, 0x00, 0x38, 0x48, 0x08, 0x10, 0x00, // 18 circle w/ dot 0x00, 0x00, 0x00, 0x6b, 0x41, 0x63, 0x3e, 0x08, // 19 tripoint 0x08, 0x08, 0x08, 0x00, 0x14, 0x22, 0x41, 0x00, }; void R2D_Crosshair_Update(void) { int crossdata[CS_WIDTH*CS_HEIGHT]; int c; int w, h; unsigned char *x; c = crosshair.ival; if (!crosshairimage.string) return; else if (crosshairimage.string[0] && c == 1) { shader_crosshair->defaulttextures.base = R_LoadHiResTexture (crosshairimage.string, "crosshairs", IF_NOMIPMAP|IF_NOGAMMA); if (TEXVALID(shader_crosshair->defaulttextures.base)) return; } else if (c <= 1) return; c -= 2; c = c % (sizeof(crosshair_pixels) / (CS_HEIGHT*sizeof(*crosshair_pixels))); if (!TEXVALID(ch_int_texture)) ch_int_texture = R_AllocNewTexture(CS_WIDTH, CS_HEIGHT); shader_crosshair->defaulttextures.base = ch_int_texture; Q_memset(crossdata, 0, sizeof(crossdata)); x = crosshair_pixels + (CS_HEIGHT * c); for (h = 0; h < CS_HEIGHT; h++) { int pix = x[h]; for (w = 0; w < CS_WIDTH; w++) { if (pix & 0x1) crossdata[CS_WIDTH * h + w] = 0xffffffff; pix >>= 1; } } R_Upload(ch_int_texture, NULL, TF_RGBA32, crossdata, NULL, CS_WIDTH, CS_HEIGHT, IF_NOMIPMAP|IF_NOGAMMA); } void R2D_CrosshairImage_Callback(struct cvar_s *var, char *oldvalue) { R2D_Crosshair_Update(); } void R2D_Crosshair_Callback(struct cvar_s *var, char *oldvalue) { R2D_Crosshair_Update(); } void R2D_CrosshairColor_Callback(struct cvar_s *var, char *oldvalue) { SCR_StringToRGB(var->string, ch_color, 255); ch_color[0] = bound(0, ch_color[0], 1); ch_color[1] = bound(0, ch_color[1], 1); ch_color[2] = bound(0, ch_color[2], 1); } void R2D_DrawCrosshair(void) { int x, y; int sc; float sx, sy, sizex, sizey; float size, chc; if (crosshair.ival < 1) return; // old style if (crosshair.ival == 1 && !crosshairimage.string[0]) { for (sc = 0; sc < cl.splitclients; sc++) { SCR_CrosshairPosition(sc, &x, &y); Font_BeginString(font_conchar, x, y, &x, &y); x -= Font_CharWidth('+' | 0xe000 | CON_WHITEMASK)/2; y -= Font_CharHeight()/2; Font_ForceColour(ch_color[0], ch_color[1], ch_color[2], crosshairalpha.value); Font_DrawChar(x, y, '+' | 0xe000 | CON_WHITEMASK); Font_InvalidateColour(); Font_EndString(font_conchar); } return; } size = crosshairsize.value; if (size < 0) { size = -size; sizex = size; sizey = size; chc = 0; } else { sizex = (size*vid.rotpixelwidth) / (float)vid.width; sizey = (size*vid.rotpixelheight) / (float)vid.height; chc = size / 16.0; } sizex = (int)sizex; sizex = ((sizex)*(int)vid.width) / (float)vid.rotpixelwidth; sizey = (int)sizey; sizey = ((sizey)*(int)vid.height) / (float)vid.rotpixelheight; R2D_ImageColours(ch_color[0], ch_color[1], ch_color[2], crosshairalpha.value); for (sc = 0; sc < cl.splitclients; sc++) { SCR_CrosshairPosition(sc, &x, &y); //translate to pixel coord, for rounding x = ((x-sizex-chc)*vid.rotpixelwidth) / (float)vid.width; y = ((y-sizey-chc)*vid.rotpixelheight) / (float)vid.height; //translate to screen coords sx = ((x)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((y)*(int)vid.height) / (float)vid.rotpixelheight; R2D_Image(sx, sy, sizex*2, sizey*2, 0, 0, 1, 1, shader_crosshair); } R2D_ImageColours(1, 1, 1, 1); } #endif